@@ -3,6 +3,9 @@ package ui
33import (
44 "encoding/json"
55 "fmt"
6+ "github.com/charmbracelet/bubbles/list"
7+ tea "github.com/charmbracelet/bubbletea"
8+ "github.com/charmbracelet/lipgloss"
69 "os"
710 "path/filepath"
811)
@@ -158,3 +161,232 @@ func InitSettings() {
158161 ApplySettings ()
159162 }
160163}
164+
165+ type SettingsItem struct {
166+ title string
167+ options []string
168+ details string
169+ selected int
170+ expanded bool
171+ key string
172+ }
173+
174+ func (i SettingsItem ) Title () string {
175+ arrow := "→"
176+ if i .expanded {
177+ arrow = "↓"
178+ }
179+ return fmt .Sprintf ("%s %s" , arrow , i .title )
180+ }
181+
182+ func (i SettingsItem ) Description () string {
183+ return fmt .Sprintf ("%s: %s" , i .options [i .selected ], i .details )
184+ }
185+
186+ func (i SettingsItem ) FilterValue () string { return i .title }
187+
188+ type SettingsModel struct {
189+ list list.Model
190+ height , width int
191+ settings UserSettings
192+ }
193+
194+ func createSettingsItems (settings UserSettings ) []list.Item {
195+ themeOptions := []string {"default" , "dark" , "light" }
196+ themeSelected := 0
197+ for i , opt := range themeOptions {
198+ if opt == settings .ThemeName {
199+ themeSelected = i
200+ break
201+ }
202+ }
203+
204+ cursorOptions := []string {"block" , "underline" }
205+ cursorSelected := 0
206+ for i , opt := range cursorOptions {
207+ if opt == settings .CursorType {
208+ cursorSelected = i
209+ break
210+ }
211+ }
212+
213+ gameModeOptions := []string {GameModeNormal , GameModeSimple }
214+ gameModeSelected := 0
215+ for i , opt := range gameModeOptions {
216+ if opt == settings .GameMode {
217+ gameModeSelected = i
218+ break
219+ }
220+ }
221+
222+ textLengthOptions := []string {TextLengthShort , TextLengthMedium , TextLengthLong , TextLengthVeryLong }
223+ textLengthSelected := 0
224+ for i , opt := range textLengthOptions {
225+ if opt == settings .TextLength {
226+ textLengthSelected = i
227+ break
228+ }
229+ }
230+
231+ refreshRateOptions := []string {"5" , "10" , "15" , "20" , "30" }
232+ refreshRateSelected := 0
233+ for i , opt := range refreshRateOptions {
234+ if opt == fmt .Sprintf ("%d" , settings .RefreshRate ) {
235+ refreshRateSelected = i
236+ break
237+ }
238+ }
239+
240+ return []list.Item {
241+ & SettingsItem {
242+ title : "Theme" ,
243+ options : themeOptions ,
244+ details : "Select your preferred theme" ,
245+ selected : themeSelected ,
246+ key : "theme" ,
247+ },
248+ & SettingsItem {
249+ title : "Cursor Type" ,
250+ options : cursorOptions ,
251+ details : "Choose cursor appearance" ,
252+ selected : cursorSelected ,
253+ key : "cursor" ,
254+ },
255+ & SettingsItem {
256+ title : "Game Mode" ,
257+ options : gameModeOptions ,
258+ details : "Select game difficulty mode" ,
259+ selected : gameModeSelected ,
260+ key : "game_mode" ,
261+ },
262+ & SettingsItem {
263+ title : "Text Length" ,
264+ options : textLengthOptions ,
265+ details : "Choose text length for typing" ,
266+ selected : textLengthSelected ,
267+ key : "text_length" ,
268+ },
269+ & SettingsItem {
270+ title : "Refresh Rate" ,
271+ options : refreshRateOptions ,
272+ details : "Set UI refresh rate (FPS)" ,
273+ selected : refreshRateSelected ,
274+ key : "refresh_rate" ,
275+ },
276+ }
277+ }
278+
279+ func initialSettingsModel () SettingsModel {
280+ settings := CurrentSettings
281+ items := createSettingsItems (settings )
282+
283+ l := list .New (items , list .NewDefaultDelegate (), 0 , 0 )
284+ l .SetShowHelp (false )
285+ l .SetFilteringEnabled (false )
286+ l .SetShowStatusBar (false )
287+ l .Title = "Settings"
288+ l .Styles .Title = SettingsTitleStyle
289+
290+ return SettingsModel {
291+ list : l ,
292+ settings : settings ,
293+ }
294+ }
295+
296+ func (m SettingsModel ) Init () tea.Cmd { return nil }
297+
298+ func (m SettingsModel ) Update (msg tea.Msg ) (tea.Model , tea.Cmd ) {
299+ switch msg := msg .(type ) {
300+ case tea.WindowSizeMsg :
301+ m .width , m .height = msg .Width , msg .Height
302+ m .list .SetSize (m .width / 3 , m .height / 2 )
303+
304+ case tea.KeyMsg :
305+ switch msg .String () {
306+ case "q" , "ctrl+c" :
307+ return m , tea .Quit
308+ case "enter" :
309+ if i , ok := m .list .SelectedItem ().(* SettingsItem ); ok {
310+ if ! i .expanded {
311+ i .expanded = true
312+ } else {
313+ i .selected = (i .selected + 1 ) % len (i .options )
314+ switch i .key {
315+ case "theme" :
316+ m .settings .ThemeName = i .options [i .selected ]
317+ case "cursor" :
318+ m .settings .CursorType = i .options [i .selected ]
319+ case "game_mode" :
320+ m .settings .GameMode = i .options [i .selected ]
321+ case "text_length" :
322+ m .settings .TextLength = i .options [i .selected ]
323+ case "refresh_rate" :
324+ fmt .Sscanf (i .options [i .selected ], "%d" , & m .settings .RefreshRate )
325+ }
326+ UpdateSettings (m .settings )
327+ }
328+ }
329+ case "esc" :
330+ if i , ok := m .list .SelectedItem ().(* SettingsItem ); ok {
331+ i .expanded = false
332+ }
333+ }
334+ }
335+
336+ var cmd tea.Cmd
337+ m .list , cmd = m .list .Update (msg )
338+ return m , cmd
339+ }
340+
341+ func (m SettingsModel ) View () string {
342+ if m .width == 0 {
343+ return "Loading..."
344+ }
345+
346+ listView := SettingsListStyle .Render (m .list .View ())
347+
348+ details := "Select an item to view details"
349+ if i , ok := m .list .SelectedItem ().(* SettingsItem ); ok {
350+ if i .expanded {
351+ details = fmt .Sprintf ("%s\n \n Current: %s\n \n Options:\n " ,
352+ i .details ,
353+ i .options [i .selected ],
354+ )
355+ for idx , opt := range i .options {
356+ bullet := "•"
357+ if idx == i .selected {
358+ bullet = ">"
359+ }
360+ details += fmt .Sprintf ("%s %s\n " , bullet , opt )
361+ }
362+ } else {
363+ details = i .details
364+ }
365+ }
366+
367+ detailsView := SettingsDetailsStyle .Render (details )
368+
369+ content := lipgloss .Place (
370+ m .width ,
371+ m .height - 1 ,
372+ lipgloss .Center ,
373+ lipgloss .Center ,
374+ lipgloss .JoinHorizontal (lipgloss .Top , listView , detailsView ),
375+ )
376+
377+ help := lipgloss .PlaceHorizontal (
378+ m .width ,
379+ lipgloss .Center ,
380+ SettingsHelpStyle .Render ("↑/↓: Navigate • Enter: Select • Esc: Back • q: Quit" ),
381+ )
382+
383+ return lipgloss .JoinVertical (lipgloss .Bottom , content , help )
384+ }
385+
386+ func ShowSettings () error {
387+ p := tea .NewProgram (initialSettingsModel (), tea .WithAltScreen ())
388+ if _ , err := p .Run (); err != nil {
389+ return fmt .Errorf ("error running settings program: %w" , err )
390+ }
391+ return nil
392+ }
0 commit comments