package tui import ( "fmt" "strings" "charm.land/bubbles/v2/list" "charm.land/bubbles/v2/textinput" tea "charm.land/bubbletea/v2" "gitgud.io/mike/mpv-manager/pkg/modernzconf" ) // modernzOptionItem implements list.Item for a ModernZ option type modernzOptionItem struct { key string label string value string def string recommended string valueType string choiceLabels map[string]string description string modified bool } func (i modernzOptionItem) Title() string { title := i.label if title == "" { title = i.key } if i.modified { title += " " + warningStyle.Render("●") } if i.recommended != "" && i.value == i.recommended { title += " " + successStyle.Render("(Recommended)") } return title } func (i modernzOptionItem) Description() string { desc := "Value: " + i.displayValue(i.value) if i.modified { desc += fmt.Sprintf(" (upstream default: %s)", i.displayValue(i.def)) } if i.label != "" { desc += " | Config key: " + i.key } return desc + " | " + i.description } func (i modernzOptionItem) FilterValue() string { return i.key + " " + i.label } func (i modernzOptionItem) displayValue(value string) string { if label := i.choiceLabels[value]; label != "" { return label } return value } func modernzOptionLabel(key string) string { for _, opt := range modernzconf.KnownOptions() { if opt.Key == key && opt.Label != "" { return opt.Label } } return key } // setupModernZOptionsList loads modernz.conf and builds the options list func (m *Model) setupModernZOptionsList() { m.refreshUIConfigStatus() m.modernzConf = nil m.modernzStatusMsg = "" m.modernzEditKey = "" path, err := modernzconf.GetModernZConfPath() if err != nil { m.modernzStatusMsg = fmt.Sprintf("✗ Failed to determine modernz.conf path: %v", err) } else { conf, err := modernzconf.ParseModernZConf(path) if err != nil { m.modernzStatusMsg = fmt.Sprintf("✗ Failed to read modernz.conf: %v", err) } else { m.modernzConf = conf } } m.rebuildModernZList() } // rebuildModernZList refreshes the options list from the loaded modernz.conf func (m *Model) rebuildModernZList() { idx := m.modernzList.Index() items := []list.Item{} for _, opt := range modernzconf.KnownOptions() { value := opt.Default if m.modernzConf != nil { value = m.modernzConf.GetValue(opt.Key) } items = append(items, modernzOptionItem{ key: opt.Key, label: opt.Label, value: value, def: opt.Default, recommended: opt.Recommended, valueType: opt.ValueType, choiceLabels: opt.ChoiceLabels, description: opt.Description, modified: value != opt.Default, }) } m.modernzList = m.createStyledList("ModernZ OSC Options", items) if idx >= len(items) { idx = len(items) - 1 } if idx >= 0 { m.modernzList.Select(idx) } } // setModernZValue sets and saves a ModernZ option value (SetValue validates first) func (m *Model) setModernZValue(key, value string) error { if m.modernzConf == nil { return fmt.Errorf("modernz.conf is not loaded") } path, err := modernzconf.GetModernZConfPath() if err != nil { return err } if err := modernzconf.SetValueFile(path, key, value); err != nil { return fmt.Errorf("failed to write modernz.conf: %w", err) } refreshed, err := modernzconf.ParseModernZConf(path) if err != nil { return fmt.Errorf("saved option but failed to reload modernz.conf: %w", err) } m.modernzConf = refreshed return nil } // toggleModernZBool flips a yes/no option and saves it func (m *Model) toggleModernZBool(item modernzOptionItem) { newValue := "yes" if item.value == "yes" { newValue = "no" } if err := m.setModernZValue(item.key, newValue); err != nil { m.modernzStatusMsg = fmt.Sprintf("✗ %v", err) } else { m.modernzStatusMsg = fmt.Sprintf("✓ %s set to %s", modernzOptionLabel(item.key), item.displayValue(newValue)) } m.rebuildModernZList() } // cycleModernZChoice advances a choice option to its next choice and saves it func (m *Model) cycleModernZChoice(item modernzOptionItem) { for _, opt := range modernzconf.KnownOptions() { if opt.Key != item.key || len(opt.Choices) == 0 { continue } next := opt.Choices[0] for i, c := range opt.Choices { if c == item.value { next = opt.Choices[(i+1)%len(opt.Choices)] break } } if err := m.setModernZValue(item.key, next); err != nil { m.modernzStatusMsg = fmt.Sprintf("✗ %v", err) } else { m.modernzStatusMsg = fmt.Sprintf("✓ %s set to %s", modernzOptionLabel(item.key), item.displayValue(next)) } m.rebuildModernZList() return } } // resetModernZOption reverts an option to its default and saves func (m *Model) resetModernZOption(key string) { if m.modernzConf == nil { m.modernzStatusMsg = "✗ modernz.conf is not loaded" return } path, err := modernzconf.GetModernZConfPath() if err != nil { m.modernzStatusMsg = fmt.Sprintf("✗ %v", err) return } if err := modernzconf.ResetValueFile(path, key); err != nil { m.modernzStatusMsg = fmt.Sprintf("✗ Failed to write modernz.conf: %v", err) return } refreshed, err := modernzconf.ParseModernZConf(path) if err != nil { m.modernzStatusMsg = fmt.Sprintf("✗ Reset option but failed to reload modernz.conf: %v", err) return } m.modernzConf = refreshed m.modernzStatusMsg = fmt.Sprintf("✓ %s reset to upstream default", modernzOptionLabel(key)) m.rebuildModernZList() } // handleModernZOptionsUpdate handles the ModernZ options list state func (m *Model) handleModernZOptionsUpdate(msg tea.Msg) (*Model, tea.Cmd) { var cmd tea.Cmd switch msg := msg.(type) { case tea.KeyPressMsg: // While filtering, let the list handle all keys if m.modernzList.SettingFilter() { break } switch keyCode(msg) { case tea.KeyEsc: if m.modernzList.IsFiltered() { m.modernzList.ResetFilter() return m, nil } m.setupConfigOptionsList() m.state = StateConfigOptions return m, nil case tea.KeyEnter: selected := m.modernzList.SelectedItem() if selected != nil { item := selected.(modernzOptionItem) switch item.valueType { case modernzconf.TypeBool: m.toggleModernZBool(item) return m, nil case modernzconf.TypeChoice: m.cycleModernZChoice(item) return m, nil default: m.modernzEditKey = item.key ti := textinput.New() ti.Placeholder = "Enter value..." ti.SetValue(item.value) ti.CharLimit = 256 ti.SetWidth(50) ti.Focus() m.modernzTextInput = ti m.modernzStatusMsg = "" m.state = StateModernZOptionEdit return m, nil } } case tea.KeyExtended: key := string(keyTextRunes(msg)) if m.uiConfigMigration != nil && (key == "a" || key == "k") { action := "apply" message := "✓ ModernZ will use consistent UI sizing" if key == "k" { action = "keep" message = "✓ Kept existing ModernZ scaling behavior" } if err := m.resolveUIConfigMigration(action); err != nil { m.modernzStatusMsg = fmt.Sprintf("✗ %v", err) return m, nil } m.setupModernZOptionsList() m.modernzStatusMsg = message return m, nil } if key == "d" { selected := m.modernzList.SelectedItem() if selected != nil { item := selected.(modernzOptionItem) m.resetModernZOption(item.key) return m, nil } } } } m.modernzList, cmd = m.modernzList.Update(msg) return m, cmd } // handleModernZOptionEditUpdate handles the ModernZ option text input state func (m *Model) handleModernZOptionEditUpdate(msg tea.Msg) (*Model, tea.Cmd) { var tiCmd tea.Cmd m.modernzTextInput, tiCmd = m.modernzTextInput.Update(msg) switch msg := msg.(type) { case tea.KeyPressMsg: switch keyCode(msg) { case tea.KeyEsc: m.modernzStatusMsg = "" m.state = StateModernZOptions return m, nil case tea.KeyEnter: value := strings.TrimSpace(m.modernzTextInput.Value()) if err := modernzconf.ValidateValue(m.modernzEditKey, value); err != nil { m.modernzStatusMsg = fmt.Sprintf("✗ %v", err) return m, nil } if err := m.setModernZValue(m.modernzEditKey, value); err != nil { m.modernzStatusMsg = fmt.Sprintf("✗ %v", err) return m, nil } m.modernzStatusMsg = fmt.Sprintf("✓ %s set to %s", modernzOptionLabel(m.modernzEditKey), value) m.rebuildModernZList() m.state = StateModernZOptions return m, nil } } return m, tiCmd } // modernzOptionsListView renders the ModernZ options editor list func (m Model) modernzOptionsListView() string { var content strings.Builder content.WriteString(titleStyle.Render("🎛️ ModernZ UI Settings")) content.WriteString("\n") content.WriteString(subtitleStyle.Render("══════════════════════")) content.WriteString("\n\n") content.WriteString(infoStyle.Render("Recommended scaling: Consistent size with matching 100% windowed/fullscreen sizes.")) content.WriteString("\n") content.WriteString(infoStyle.Render("● marks options modified from their upstream default")) content.WriteString("\n\n") if m.uiConfigMigration != nil { content.WriteString(warningStyle.Render("⚠ " + m.uiConfigMigration.Title)) content.WriteString("\n") content.WriteString(infoStyle.Render(m.uiConfigMigration.Message)) content.WriteString("\n") content.WriteString(infoStyle.Render("Press ")) content.WriteString(subtitleStyle.Render("a")) content.WriteString(infoStyle.Render(" to use consistent size or ")) content.WriteString(subtitleStyle.Render("k")) content.WriteString(infoStyle.Render(" to keep the current behavior")) content.WriteString("\n\n") } if len(m.uiConfigWarnings) > 0 { content.WriteString(errorStyle.Render("Configuration values needing review:")) content.WriteString("\n") for _, warning := range m.uiConfigWarnings { content.WriteString(errorStyle.Render("• " + warning.Key + ": " + warning.Message)) content.WriteString("\n") } content.WriteString("\n") } if m.modernzStatusMsg != "" { if strings.HasPrefix(m.modernzStatusMsg, "✓") { content.WriteString(successStyle.Render(m.modernzStatusMsg)) } else { content.WriteString(errorStyle.Render(m.modernzStatusMsg)) } content.WriteString("\n\n") } content.WriteString(m.modernzList.View()) content.WriteString("\n\n") content.WriteString(infoStyle.Render("Press ")) content.WriteString(subtitleStyle.Render("Enter")) content.WriteString(infoStyle.Render(" to edit/toggle/cycle, ")) content.WriteString(subtitleStyle.Render("d")) content.WriteString(infoStyle.Render(" to reset to default, ")) content.WriteString(subtitleStyle.Render("Esc")) content.WriteString(infoStyle.Render(" to go back")) return content.String() } // modernzOptionEditView renders the ModernZ option text input func (m Model) modernzOptionEditView() string { var content strings.Builder content.WriteString(titleStyle.Render(fmt.Sprintf("Edit %s", modernzOptionLabel(m.modernzEditKey)))) content.WriteString("\n") content.WriteString(subtitleStyle.Render("══════════════════════")) content.WriteString("\n\n") content.WriteString(m.modernzTextInput.View()) content.WriteString("\n\n") if m.modernzStatusMsg != "" { content.WriteString(errorStyle.Render(m.modernzStatusMsg)) content.WriteString("\n\n") } content.WriteString(infoStyle.Render("Press ")) content.WriteString(subtitleStyle.Render("Enter")) content.WriteString(infoStyle.Render(" to save, ")) content.WriteString(subtitleStyle.Render("Esc")) content.WriteString(infoStyle.Render(" to cancel")) return content.String() }