From c7ac82f306d2fe273e8cf422bc7f6fc4852b2516 Mon Sep 17 00:00:00 2001 From: Stu Leak Date: Thu, 1 Jan 2026 19:45:27 -0500 Subject: [PATCH] feat(ui): Redesign Convert module with color-coded dropdown buttons MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Major UI improvement: Integrate color indicators directly into dropdown buttons instead of showing separate badge elements, creating a cleaner, more intuitive interface where power users can quickly identify format/codec selections by color. Changes: - Add NewColorCodedSelectContainer() in internal/ui/components.go - Creates colored 4px left border on dropdowns - Returns container and border reference for dynamic color updates - Update Format Selection: - Colored border matches container format (MKV=teal, MP4=blue, etc.) - Dynamic color updates when format changes - Remove old formatBadgeContainer approach - Update Video Codec Selection: - Colored border matches codec (H.264=sky blue, H.265=lime, AV1=emerald, etc.) - Applied to Advanced tab - Update Audio Codec Selection: - Colored border matches codec (AAC=purple, Opus=violet, MP3=rose, etc.) - Applied to Advanced tab Color system provides instant visual feedback and helps power users navigate settings quickly. Each format/codec has a unique color that's consistent throughout the UI. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 --- internal/ui/components.go | 13 +++++ main.go | 102 +++++++++++++++++++++++++------------- 2 files changed, 80 insertions(+), 35 deletions(-) diff --git a/internal/ui/components.go b/internal/ui/components.go index 8d70cce..490fad0 100644 --- a/internal/ui/components.go +++ b/internal/ui/components.go @@ -998,3 +998,16 @@ func ColoredDivider(accentColor color.Color) fyne.CanvasObject { divider.SetMinSize(fyne.NewSize(0, 2)) return divider } + +// NewColorCodedSelectContainer wraps a Select widget with a colored left border +// The colored border visually indicates the category/type of the selection +// Returns a container with the border and a pointer to the border rectangle for color updates +func NewColorCodedSelectContainer(selectWidget *widget.Select, accentColor color.Color) (*fyne.Container, *canvas.Rectangle) { + // Create colored left border rectangle + border := canvas.NewRectangle(accentColor) + border.SetMinSize(fyne.NewSize(4, 44)) + + // Return container with [ColoredBorder][Select] and the border for future updates + container := container.NewBorder(nil, nil, border, nil, selectWidget) + return container, border +} diff --git a/main.go b/main.go index 0f3008e..e1b6bf2 100644 --- a/main.go +++ b/main.go @@ -6972,15 +6972,17 @@ func buildConvertView(state *appState, src *videoSource) fyne.CanvasObject { // Cover art display on one line coverDisplay = widget.NewLabel("Cover Art: " + state.convert.CoverLabel()) - // Create video codec badge with semantic color - videoCodecBadgeContainer := container.NewMax() - updateVideoCodecBadge := func(codecName string) { - videoCodecBadgeContainer.Objects = []fyne.CanvasObject{buildVideoCodecBadge(codecName)} - videoCodecBadgeContainer.Refresh() - } + // Create video codec select widget with color-coded left border + videoCodecSelect := widget.NewSelect([]string{"H.264", "H.265", "VP9", "AV1", "MPEG-2", "Copy"}, nil) // Callback set below - // Video Codec selection - videoCodecSelect := widget.NewSelect([]string{"H.264", "H.265", "VP9", "AV1", "MPEG-2", "Copy"}, func(value string) { + // Get initial color for selected video codec + initialVideoCodecColor := ui.GetVideoCodecColor(state.convert.VideoCodec) + + // Wrap in color-coded container + videoCodecContainer, videoCodecBorder := ui.NewColorCodedSelectContainer(videoCodecSelect, initialVideoCodecColor) + + // Set video codec select callback (now that we have videoCodecBorder reference) + videoCodecSelect.OnChanged = func(value string) { state.convert.VideoCodec = value logging.Debug(logging.CatUI, "video codec set to %s", value) if updateQualityOptions != nil { @@ -6995,10 +6997,13 @@ func buildConvertView(state *appState, src *videoSource) fyne.CanvasObject { if buildCommandPreview != nil { buildCommandPreview() } - updateVideoCodecBadge(value) - }) + + // Update border color to match new codec + newColor := ui.GetVideoCodecColor(value) + videoCodecBorder.FillColor = newColor + videoCodecBorder.Refresh() + } videoCodecSelect.SetSelected(state.convert.VideoCodec) - updateVideoCodecBadge(state.convert.VideoCodec) // Map format preset codec names to the UI-facing codec selector value mapFormatCodec := func(codec string) string { @@ -7035,14 +7040,32 @@ func buildConvertView(state *appState, src *videoSource) fyne.CanvasObject { } } - // Create format badge with semantic color - formatBadgeContainer := container.NewMax() - updateFormatBadge := func(label string) { - formatBadgeContainer.Objects = []fyne.CanvasObject{buildFormatBadge(label)} - formatBadgeContainer.Refresh() + // Create format select widget with color-coded left border + formatSelect := widget.NewSelect(formatLabels, nil) // Callback set below + + // Parse format name from label (e.g., "MKV (AV1)" -> "mkv") + parseFormat := func(label string) string { + // Extract container format from label + parts := strings.Split(label, " ") + if len(parts) > 0 { + format := strings.ToLower(parts[0]) + // Special case: "REMUX" should use remux color + if strings.Contains(strings.ToUpper(label), "REMUX") { + return "remux" + } + return format + } + return "mp4" // fallback } - updateFormatBadge(state.convert.SelectedFormat.Label) - formatSelect := widget.NewSelect(formatLabels, func(value string) { + + // Get initial color for selected format + initialFormatColor := ui.GetContainerColor(parseFormat(state.convert.SelectedFormat.Label)) + + // Wrap in color-coded container + formatContainer, formatBorder := ui.NewColorCodedSelectContainer(formatSelect, initialFormatColor) + + // Set format select callback (now that we have formatBorder reference) + formatSelect.OnChanged = func(value string) { for _, opt := range formatOptions { if opt.Label == value { logging.Debug(logging.CatUI, "format set to %s", value) @@ -7076,11 +7099,15 @@ func buildConvertView(state *appState, src *videoSource) fyne.CanvasObject { if buildCommandPreview != nil { buildCommandPreview() } - updateFormatBadge(value) + + // Update border color to match new format + newColor := ui.GetContainerColor(parseFormat(value)) + formatBorder.FillColor = newColor + formatBorder.Refresh() break } } - }) + } formatSelect.SetSelected(state.convert.SelectedFormat.Label) updateChapterWarning() // Initial visibility @@ -8038,21 +8065,26 @@ func buildConvertView(state *appState, src *videoSource) fyne.CanvasObject { }) twoPassCheck.Checked = state.convert.TwoPass - // Create audio codec badge with semantic color - audioCodecBadgeContainer := container.NewMax() - updateAudioCodecBadge := func(codecName string) { - audioCodecBadgeContainer.Objects = []fyne.CanvasObject{buildAudioCodecBadge(codecName)} - audioCodecBadgeContainer.Refresh() - } + // Create audio codec select widget with color-coded left border + audioCodecSelect = widget.NewSelect([]string{"AAC", "Opus", "MP3", "FLAC", "Copy"}, nil) // Callback set below - // Audio Codec - audioCodecSelect = widget.NewSelect([]string{"AAC", "Opus", "MP3", "FLAC", "Copy"}, func(value string) { + // Get initial color for selected audio codec + initialAudioCodecColor := ui.GetAudioCodecColor(state.convert.AudioCodec) + + // Wrap in color-coded container + audioCodecContainer, audioCodecBorder := ui.NewColorCodedSelectContainer(audioCodecSelect, initialAudioCodecColor) + + // Set audio codec select callback (now that we have audioCodecBorder reference) + audioCodecSelect.OnChanged = func(value string) { state.convert.AudioCodec = value logging.Debug(logging.CatUI, "audio codec set to %s", value) - updateAudioCodecBadge(value) - }) + + // Update border color to match new codec + newColor := ui.GetAudioCodecColor(value) + audioCodecBorder.FillColor = newColor + audioCodecBorder.Refresh() + } audioCodecSelect.SetSelected(state.convert.AudioCodec) - updateAudioCodecBadge(state.convert.AudioCodec) // Audio Bitrate audioBitrateSelect := widget.NewSelect([]string{"128k", "192k", "256k", "320k"}, func(value string) { @@ -8334,7 +8366,7 @@ func buildConvertView(state *appState, src *videoSource) fyne.CanvasObject { simpleOptions := container.NewVBox( widget.NewLabelWithStyle("═══ OUTPUT ═══", fyne.TextAlignCenter, fyne.TextStyle{Bold: true}), widget.NewLabelWithStyle("Format", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}), - container.NewHBox(formatSelect, formatBadgeContainer), + formatContainer, chapterWarningLabel, // Warning when converting chapters to DVD preserveChaptersCheck, dvdAspectBox, // DVD options appear here when DVD format selected @@ -8359,7 +8391,7 @@ func buildConvertView(state *appState, src *videoSource) fyne.CanvasObject { advancedVideoEncodingBlock = container.NewVBox( widget.NewLabelWithStyle("═══ VIDEO ENCODING ═══", fyne.TextAlignCenter, fyne.TextStyle{Bold: true}), widget.NewLabelWithStyle("Video Codec", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}), - container.NewHBox(videoCodecSelect, videoCodecBadgeContainer), + videoCodecContainer, widget.NewLabelWithStyle("Encoder Preset (speed vs quality)", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}), encoderPresetSelect, encoderPresetHintContainer, @@ -8387,7 +8419,7 @@ func buildConvertView(state *appState, src *videoSource) fyne.CanvasObject { audioEncodingSection = container.NewVBox( widget.NewLabelWithStyle("═══ AUDIO ENCODING ═══", fyne.TextAlignCenter, fyne.TextStyle{Bold: true}), widget.NewLabelWithStyle("Audio Codec", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}), - container.NewHBox(audioCodecSelect, audioCodecBadgeContainer), + audioCodecContainer, widget.NewLabelWithStyle("Audio Bitrate", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}), audioBitrateSelect, widget.NewLabelWithStyle("Audio Channels", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}), @@ -8397,7 +8429,7 @@ func buildConvertView(state *appState, src *videoSource) fyne.CanvasObject { advancedOptions := container.NewVBox( widget.NewLabelWithStyle("═══ OUTPUT ═══", fyne.TextAlignCenter, fyne.TextStyle{Bold: true}), widget.NewLabelWithStyle("Format", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}), - container.NewHBox(formatSelect, formatBadgeContainer), + formatContainer, chapterWarningLabel, // Warning when converting chapters to DVD preserveChaptersCheck, dvdAspectBox, // DVD options appear here when DVD format selected