feat(ui): Redesign Convert module with color-coded dropdown buttons
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.
This commit is contained in:
parent
ed48c0bc0d
commit
c68dfd0e2c
|
|
@ -998,3 +998,16 @@ func ColoredDivider(accentColor color.Color) fyne.CanvasObject {
|
||||||
divider.SetMinSize(fyne.NewSize(0, 2))
|
divider.SetMinSize(fyne.NewSize(0, 2))
|
||||||
return divider
|
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
|
||||||
|
}
|
||||||
|
|
|
||||||
102
main.go
102
main.go
|
|
@ -6972,15 +6972,17 @@ func buildConvertView(state *appState, src *videoSource) fyne.CanvasObject {
|
||||||
// Cover art display on one line
|
// Cover art display on one line
|
||||||
coverDisplay = widget.NewLabel("Cover Art: " + state.convert.CoverLabel())
|
coverDisplay = widget.NewLabel("Cover Art: " + state.convert.CoverLabel())
|
||||||
|
|
||||||
// Create video codec badge with semantic color
|
// Create video codec select widget with color-coded left border
|
||||||
videoCodecBadgeContainer := container.NewMax()
|
videoCodecSelect := widget.NewSelect([]string{"H.264", "H.265", "VP9", "AV1", "MPEG-2", "Copy"}, nil) // Callback set below
|
||||||
updateVideoCodecBadge := func(codecName string) {
|
|
||||||
videoCodecBadgeContainer.Objects = []fyne.CanvasObject{buildVideoCodecBadge(codecName)}
|
|
||||||
videoCodecBadgeContainer.Refresh()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Video Codec selection
|
// Get initial color for selected video codec
|
||||||
videoCodecSelect := widget.NewSelect([]string{"H.264", "H.265", "VP9", "AV1", "MPEG-2", "Copy"}, func(value string) {
|
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
|
state.convert.VideoCodec = value
|
||||||
logging.Debug(logging.CatUI, "video codec set to %s", value)
|
logging.Debug(logging.CatUI, "video codec set to %s", value)
|
||||||
if updateQualityOptions != nil {
|
if updateQualityOptions != nil {
|
||||||
|
|
@ -6995,10 +6997,13 @@ func buildConvertView(state *appState, src *videoSource) fyne.CanvasObject {
|
||||||
if buildCommandPreview != nil {
|
if buildCommandPreview != nil {
|
||||||
buildCommandPreview()
|
buildCommandPreview()
|
||||||
}
|
}
|
||||||
updateVideoCodecBadge(value)
|
|
||||||
})
|
// Update border color to match new codec
|
||||||
|
newColor := ui.GetVideoCodecColor(value)
|
||||||
|
videoCodecBorder.FillColor = newColor
|
||||||
|
videoCodecBorder.Refresh()
|
||||||
|
}
|
||||||
videoCodecSelect.SetSelected(state.convert.VideoCodec)
|
videoCodecSelect.SetSelected(state.convert.VideoCodec)
|
||||||
updateVideoCodecBadge(state.convert.VideoCodec)
|
|
||||||
|
|
||||||
// Map format preset codec names to the UI-facing codec selector value
|
// Map format preset codec names to the UI-facing codec selector value
|
||||||
mapFormatCodec := func(codec string) string {
|
mapFormatCodec := func(codec string) string {
|
||||||
|
|
@ -7035,14 +7040,32 @@ func buildConvertView(state *appState, src *videoSource) fyne.CanvasObject {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create format badge with semantic color
|
// Create format select widget with color-coded left border
|
||||||
formatBadgeContainer := container.NewMax()
|
formatSelect := widget.NewSelect(formatLabels, nil) // Callback set below
|
||||||
updateFormatBadge := func(label string) {
|
|
||||||
formatBadgeContainer.Objects = []fyne.CanvasObject{buildFormatBadge(label)}
|
// Parse format name from label (e.g., "MKV (AV1)" -> "mkv")
|
||||||
formatBadgeContainer.Refresh()
|
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 {
|
for _, opt := range formatOptions {
|
||||||
if opt.Label == value {
|
if opt.Label == value {
|
||||||
logging.Debug(logging.CatUI, "format set to %s", 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 {
|
if buildCommandPreview != nil {
|
||||||
buildCommandPreview()
|
buildCommandPreview()
|
||||||
}
|
}
|
||||||
updateFormatBadge(value)
|
|
||||||
|
// Update border color to match new format
|
||||||
|
newColor := ui.GetContainerColor(parseFormat(value))
|
||||||
|
formatBorder.FillColor = newColor
|
||||||
|
formatBorder.Refresh()
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
formatSelect.SetSelected(state.convert.SelectedFormat.Label)
|
formatSelect.SetSelected(state.convert.SelectedFormat.Label)
|
||||||
|
|
||||||
updateChapterWarning() // Initial visibility
|
updateChapterWarning() // Initial visibility
|
||||||
|
|
@ -8038,21 +8065,26 @@ func buildConvertView(state *appState, src *videoSource) fyne.CanvasObject {
|
||||||
})
|
})
|
||||||
twoPassCheck.Checked = state.convert.TwoPass
|
twoPassCheck.Checked = state.convert.TwoPass
|
||||||
|
|
||||||
// Create audio codec badge with semantic color
|
// Create audio codec select widget with color-coded left border
|
||||||
audioCodecBadgeContainer := container.NewMax()
|
audioCodecSelect = widget.NewSelect([]string{"AAC", "Opus", "MP3", "FLAC", "Copy"}, nil) // Callback set below
|
||||||
updateAudioCodecBadge := func(codecName string) {
|
|
||||||
audioCodecBadgeContainer.Objects = []fyne.CanvasObject{buildAudioCodecBadge(codecName)}
|
|
||||||
audioCodecBadgeContainer.Refresh()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Audio Codec
|
// Get initial color for selected audio codec
|
||||||
audioCodecSelect = widget.NewSelect([]string{"AAC", "Opus", "MP3", "FLAC", "Copy"}, func(value string) {
|
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
|
state.convert.AudioCodec = value
|
||||||
logging.Debug(logging.CatUI, "audio codec set to %s", 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)
|
audioCodecSelect.SetSelected(state.convert.AudioCodec)
|
||||||
updateAudioCodecBadge(state.convert.AudioCodec)
|
|
||||||
|
|
||||||
// Audio Bitrate
|
// Audio Bitrate
|
||||||
audioBitrateSelect := widget.NewSelect([]string{"128k", "192k", "256k", "320k"}, func(value string) {
|
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(
|
simpleOptions := container.NewVBox(
|
||||||
widget.NewLabelWithStyle("═══ OUTPUT ═══", fyne.TextAlignCenter, fyne.TextStyle{Bold: true}),
|
widget.NewLabelWithStyle("═══ OUTPUT ═══", fyne.TextAlignCenter, fyne.TextStyle{Bold: true}),
|
||||||
widget.NewLabelWithStyle("Format", fyne.TextAlignLeading, 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
|
chapterWarningLabel, // Warning when converting chapters to DVD
|
||||||
preserveChaptersCheck,
|
preserveChaptersCheck,
|
||||||
dvdAspectBox, // DVD options appear here when DVD format selected
|
dvdAspectBox, // DVD options appear here when DVD format selected
|
||||||
|
|
@ -8359,7 +8391,7 @@ func buildConvertView(state *appState, src *videoSource) fyne.CanvasObject {
|
||||||
advancedVideoEncodingBlock = container.NewVBox(
|
advancedVideoEncodingBlock = container.NewVBox(
|
||||||
widget.NewLabelWithStyle("═══ VIDEO ENCODING ═══", fyne.TextAlignCenter, fyne.TextStyle{Bold: true}),
|
widget.NewLabelWithStyle("═══ VIDEO ENCODING ═══", fyne.TextAlignCenter, fyne.TextStyle{Bold: true}),
|
||||||
widget.NewLabelWithStyle("Video Codec", fyne.TextAlignLeading, 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}),
|
widget.NewLabelWithStyle("Encoder Preset (speed vs quality)", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}),
|
||||||
encoderPresetSelect,
|
encoderPresetSelect,
|
||||||
encoderPresetHintContainer,
|
encoderPresetHintContainer,
|
||||||
|
|
@ -8387,7 +8419,7 @@ func buildConvertView(state *appState, src *videoSource) fyne.CanvasObject {
|
||||||
audioEncodingSection = container.NewVBox(
|
audioEncodingSection = container.NewVBox(
|
||||||
widget.NewLabelWithStyle("═══ AUDIO ENCODING ═══", fyne.TextAlignCenter, fyne.TextStyle{Bold: true}),
|
widget.NewLabelWithStyle("═══ AUDIO ENCODING ═══", fyne.TextAlignCenter, fyne.TextStyle{Bold: true}),
|
||||||
widget.NewLabelWithStyle("Audio Codec", fyne.TextAlignLeading, 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}),
|
widget.NewLabelWithStyle("Audio Bitrate", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}),
|
||||||
audioBitrateSelect,
|
audioBitrateSelect,
|
||||||
widget.NewLabelWithStyle("Audio Channels", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}),
|
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(
|
advancedOptions := container.NewVBox(
|
||||||
widget.NewLabelWithStyle("═══ OUTPUT ═══", fyne.TextAlignCenter, fyne.TextStyle{Bold: true}),
|
widget.NewLabelWithStyle("═══ OUTPUT ═══", fyne.TextAlignCenter, fyne.TextStyle{Bold: true}),
|
||||||
widget.NewLabelWithStyle("Format", fyne.TextAlignLeading, 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
|
chapterWarningLabel, // Warning when converting chapters to DVD
|
||||||
preserveChaptersCheck,
|
preserveChaptersCheck,
|
||||||
dvdAspectBox, // DVD options appear here when DVD format selected
|
dvdAspectBox, // DVD options appear here when DVD format selected
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user