diff options
| author | kj_sh604 | 2026-03-15 12:56:15 -0400 |
|---|---|---|
| committer | kj_sh604 | 2026-03-15 12:56:15 -0400 |
| commit | 3960458765142f7e1475ed4c662dffe3e73a0255 (patch) | |
| tree | a16a4fe8e9b75d46421e442df37e2a4ba8e0bc7f /src/main.go | |
| parent | 737c95d40a2a89f6ff117413aa3a31159e41b7af (diff) | |
refactor: more color palettes and layout changes
Diffstat (limited to 'src/main.go')
| -rw-r--r-- | src/main.go | 284 |
1 files changed, 241 insertions, 43 deletions
diff --git a/src/main.go b/src/main.go index f2e85f3..df5ecef 100644 --- a/src/main.go +++ b/src/main.go @@ -22,8 +22,8 @@ const ( appTitle = "kjagave" appVersion = "20260315-0200" maxHistoryLen = 250 - cardImageW = 150 - cardImageH = 98 + cardImageW = 160 + cardImageH = 132 ) type SavedColor struct { @@ -43,6 +43,8 @@ type SwatchCard struct { image *gtk.Image label *gtk.Label hex string + rgb string + hsv string } type App struct { @@ -97,9 +99,18 @@ var schemeNames = []string{ } var paletteNames = []string{ - "Web-safe colors", - "Tango", - "Visibone Core", + "Web-safe (legacy)", + "Material Design", + "Tailwind CSS", + "Flat UI", + "Pastel", + "Nord", + "Dracula", + "Solarized", + "Gruvbox", + "One Dark", + "Monokai", + "KiJiSH Dark Pastel Terminal", } func main() { @@ -196,11 +207,22 @@ func (app *App) createUI() { root.PackStart(toolbar, false, false, 0) - schemeRow, _ := gtk.BoxNew(gtk.ORIENTATION_HORIZONTAL, 4) + schemeRow, _ := gtk.BoxNew(gtk.ORIENTATION_HORIZONTAL, 2) app.swatchCards = make([]SwatchCard, 0, 4) for i := 0; i < 4; i++ { card := app.newSwatchCard() cardIdx := i + card.button.Connect("button-press-event", func(_ *gtk.Button, ev *gdk.Event) bool { + if ev == nil { + return false + } + evBtn := gdk.EventButtonNewFromEvent(ev) + if evBtn == nil || evBtn.Button() != 3 { + return false + } + app.showSwatchContextMenu(cardIdx, ev) + return true + }) card.button.Connect("clicked", func() { hex := app.swatchCards[cardIdx].hex if hex == "" { @@ -271,14 +293,14 @@ func (app *App) createUI() { root.PackStart(controlRow, false, false, 0) - lower, _ := gtk.BoxNew(gtk.ORIENTATION_HORIZONTAL, 6) + lower, _ := gtk.BoxNew(gtk.ORIENTATION_HORIZONTAL, 4) paletteFrame, _ := gtk.FrameNew("Palette") - paletteBox, _ := gtk.BoxNew(gtk.ORIENTATION_VERTICAL, 6) - paletteBox.SetMarginTop(5) - paletteBox.SetMarginBottom(5) - paletteBox.SetMarginStart(5) - paletteBox.SetMarginEnd(5) + paletteBox, _ := gtk.BoxNew(gtk.ORIENTATION_VERTICAL, 2) + paletteBox.SetMarginTop(2) + paletteBox.SetMarginBottom(2) + paletteBox.SetMarginStart(2) + paletteBox.SetMarginEnd(2) app.paletteScroll, _ = gtk.ScrolledWindowNew(nil, nil) app.paletteScroll.SetPolicy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) @@ -303,11 +325,11 @@ func (app *App) createUI() { lower.PackStart(paletteFrame, true, true, 0) favFrame, _ := gtk.FrameNew("Favorites") - favBox, _ := gtk.BoxNew(gtk.ORIENTATION_VERTICAL, 6) - favBox.SetMarginTop(5) - favBox.SetMarginBottom(5) - favBox.SetMarginStart(5) - favBox.SetMarginEnd(5) + favBox, _ := gtk.BoxNew(gtk.ORIENTATION_VERTICAL, 2) + favBox.SetMarginTop(2) + favBox.SetMarginBottom(2) + favBox.SetMarginStart(2) + favBox.SetMarginEnd(2) favScroll, _ := gtk.ScrolledWindowNew(nil, nil) favScroll.SetPolicy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) @@ -429,7 +451,7 @@ func (app *App) buildMenuBar() *gtk.MenuBar { func (app *App) initCompactButtonCSS() { app.css, _ = gtk.CssProviderNew() - css := "button { padding: 1px 4px; min-height: 0; min-width: 0; } .palette-swatch { padding: 0; border-width: 0; border-radius: 0; }" + css := "button { padding: 1px 4px; min-height: 0; min-width: 0; } .palette-swatch { padding: 0; border-width: 0; border-radius: 0; } .swatch-overlay-label { text-shadow: none; }" _ = app.css.LoadFromData(css) if screen, err := gdk.ScreenGetDefault(); err == nil && screen != nil { gtk.AddProviderForScreen(screen, app.css, gtk.STYLE_PROVIDER_PRIORITY_APPLICATION) @@ -441,28 +463,41 @@ func (app *App) setButtonIcon(btn *gtk.Button, iconName string) { if err != nil || img == nil { return } + if label, err := btn.GetLabel(); err == nil && strings.TrimSpace(label) != "" { + btn.SetTooltipText(label) + btn.SetLabel("") + } btn.SetImage(img) btn.SetAlwaysShowImage(true) } func (app *App) newSwatchCard() SwatchCard { button, _ := gtk.ButtonNew() - button.SetSizeRequest(170, 185) + button.SetSizeRequest(166, 138) + + vbox, _ := gtk.BoxNew(gtk.ORIENTATION_VERTICAL, 0) + vbox.SetMarginTop(1) + vbox.SetMarginBottom(1) + vbox.SetMarginStart(1) + vbox.SetMarginEnd(1) - vbox, _ := gtk.BoxNew(gtk.ORIENTATION_VERTICAL, 4) - vbox.SetMarginTop(4) - vbox.SetMarginBottom(4) - vbox.SetMarginStart(4) - vbox.SetMarginEnd(4) + overlay, _ := gtk.OverlayNew() + overlay.SetHExpand(true) + overlay.SetVExpand(true) image, _ := gtk.ImageNew() image.SetFromPixbuf(solidPixbuf("#000000", cardImageW, cardImageH)) - vbox.PackStart(image, false, false, 0) + overlay.Add(image) label, _ := gtk.LabelNew("") label.SetJustify(gtk.JUSTIFY_CENTER) label.SetHAlign(gtk.ALIGN_CENTER) - vbox.PackStart(label, false, false, 0) + label.SetVAlign(gtk.ALIGN_CENTER) + if ctx, err := label.GetStyleContext(); err == nil { + ctx.AddClass("swatch-overlay-label") + } + overlay.AddOverlay(label) + vbox.PackStart(overlay, true, true, 0) button.Add(vbox) @@ -496,6 +531,8 @@ func (app *App) updateSchemePreview() { for i := 0; i < len(app.swatchCards); i++ { if i >= len(colors) { app.swatchCards[i].hex = "" + app.swatchCards[i].rgb = "" + app.swatchCards[i].hsv = "" app.swatchCards[i].button.Hide() continue } @@ -506,10 +543,77 @@ func (app *App) updateSchemePreview() { r := int(math.Round(colors[i].GetRed() * 255)) g := int(math.Round(colors[i].GetGreen() * 255)) b := int(math.Round(colors[i].GetBlue() * 255)) + textColor := "#F5F5F5" + if luminance(colors[i]) > 0.53 { + textColor = "#111111" + } + rgbText := fmt.Sprintf("rgb(%d, %d, %d)", r, g, b) + hsvText := fmt.Sprintf("hsv(%d, %d, %d)", int(h), int(s), int(v)) app.swatchCards[i].hex = hex + app.swatchCards[i].rgb = rgbText + app.swatchCards[i].hsv = hsvText app.swatchCards[i].image.SetFromPixbuf(solidPixbuf(hex, cardImageW, cardImageH)) - app.swatchCards[i].label.SetText(fmt.Sprintf("%s\nrgb(%d, %d, %d)\nhsv(%d, %d, %d)", hex, r, g, b, int(h), int(s), int(v))) + app.swatchCards[i].label.SetMarkup(fmt.Sprintf("<span foreground=\"%s\" size=\"9000\"><b>%s</b>\n%s\n%s</span>", textColor, hex, rgbText, hsvText)) + } +} + +func (app *App) copyTextToClipboard(text string) { + if strings.TrimSpace(text) == "" { + return + } + clipboard, _ := gtk.ClipboardGet(gdk.SELECTION_CLIPBOARD) + clipboard.SetText(text) +} + +func (app *App) showSwatchContextMenu(cardIdx int, ev *gdk.Event) { + if cardIdx < 0 || cardIdx >= len(app.swatchCards) { + return + } + card := app.swatchCards[cardIdx] + if card.hex == "" { + return } + app.showColorContextMenu(card.hex, card.rgb, card.hsv, ev) +} + +func (app *App) showColorContextMenu(hex, rgbText, hsvText string, ev *gdk.Event) { + if strings.TrimSpace(hex) == "" { + return + } + + menu, _ := gtk.MenuNew() + copyHex, _ := gtk.MenuItemNewWithLabel("Copy HEX") + copyHex.Connect("activate", func() { + app.copyTextToClipboard(hex) + }) + menu.Append(copyHex) + + copyHSV, _ := gtk.MenuItemNewWithLabel("Copy HSV") + copyHSV.Connect("activate", func() { + app.copyTextToClipboard(hsvText) + }) + menu.Append(copyHSV) + + copyRGB, _ := gtk.MenuItemNewWithLabel("Copy RGB (RGV)") + copyRGB.Connect("activate", func() { + app.copyTextToClipboard(rgbText) + }) + menu.Append(copyRGB) + + menu.ShowAll() + menu.PopupAtPointer(ev) +} + +func colorStringsFromHex(hex string) (string, string) { + rgba := gdk.NewRGBA() + if !rgba.Parse(hex) { + return "", "" + } + r := int(math.Round(rgba.GetRed() * 255)) + g := int(math.Round(rgba.GetGreen() * 255)) + b := int(math.Round(rgba.GetBlue() * 255)) + h, s, v := rgbToHSV(rgba) + return fmt.Sprintf("rgb(%d, %d, %d)", r, g, b), fmt.Sprintf("hsv(%d, %d, %d)", int(h), int(s), int(v)) } func (app *App) applyHexEntry() { @@ -645,13 +749,12 @@ func (app *App) onFavoriteSelectionChanged(selection *gtk.TreeSelection) { } func (app *App) populatePaletteGrid() { - for { - child, err := app.paletteGrid.GetChildAt(0, 0) - if err != nil || child == nil { - break + children := app.paletteGrid.GetChildren() + children.Foreach(func(item interface{}) { + if widget, ok := item.(*gtk.Widget); ok { + app.paletteGrid.Remove(widget) } - app.paletteGrid.Remove(child) - } + }) colors := paletteByName(app.activePaletteName()) cols := 24 @@ -666,6 +769,18 @@ func (app *App) populatePaletteGrid() { img, _ := gtk.ImageNewFromPixbuf(solidPixbuf(hex, 16, 11)) btn.Add(img) h := hex + btn.Connect("button-press-event", func(_ *gtk.Button, ev *gdk.Event) bool { + if ev == nil { + return false + } + evBtn := gdk.EventButtonNewFromEvent(ev) + if evBtn == nil || evBtn.Button() != 3 { + return false + } + rgbText, hsvText := colorStringsFromHex(h) + app.showColorContextMenu(h, rgbText, hsvText, ev) + return true + }) btn.Connect("clicked", func() { rgba := gdk.NewRGBA() if rgba.Parse(h) { @@ -925,19 +1040,102 @@ func generateScheme(base *gdk.RGBA, schemeName string) []*gdk.RGBA { func paletteByName(name string) []string { switch name { - case "Tango": + case "Web-safe (legacy)": + vals := []int{0x00, 0x33, 0x66, 0x99, 0xCC, 0xFF} + colors := make([]string, 0, 216) + for _, r := range vals { + for _, g := range vals { + for _, b := range vals { + colors = append(colors, fmt.Sprintf("#%02X%02X%02X", r, g, b)) + } + } + } + return colors + case "Material Design": + return []string{ + "#F44336", "#E91E63", "#9C27B0", "#673AB7", "#3F51B5", "#2196F3", + "#03A9F4", "#00BCD4", "#009688", "#4CAF50", "#8BC34A", "#CDDC39", + "#FFEB3B", "#FFC107", "#FF9800", "#FF5722", "#795548", "#9E9E9E", + "#607D8B", "#000000", "#FFFFFF", "#EF5350", "#EC407A", "#AB47BC", + "#7E57C2", "#5C6BC0", "#42A5F5", "#29B6F6", "#26C6DA", "#26A69A", + "#66BB6A", "#9CCC65", "#D4E157", "#FFEE58", "#FFCA28", "#FFA726", + "#FF7043", "#8D6E63", "#BDBDBD", "#78909C", "#212121", "#FAFAFA", + "#C62828", "#AD1457", "#6A1B9A", "#4527A0", "#283593", "#1565C0", + } + case "Tailwind CSS": + return []string{ + "#EF4444", "#F97316", "#F59E0B", "#EAB308", "#84CC16", "#22C55E", + "#10B981", "#14B8A6", "#06B6D4", "#0EA5E9", "#3B82F6", "#6366F1", + "#8B5CF6", "#A855F7", "#D946EF", "#EC4899", "#F43F5E", "#64748B", + "#DC2626", "#EA580C", "#D97706", "#CA8A04", "#65A30D", "#16A34A", + "#059669", "#0D9488", "#0891B2", "#0284C7", "#2563EB", "#4F46E5", + "#7C3AED", "#9333EA", "#C026D3", "#DB2777", "#E11D48", "#475569", + "#991B1B", "#9A3412", "#92400E", "#854D0E", "#3F6212", "#14532D", + "#064E3B", "#134E4A", "#164E63", "#075985", "#1E3A8A", "#312E81", + } + case "Flat UI": + return []string{ + "#1ABC9C", "#16A085", "#2ECC71", "#27AE60", "#3498DB", "#2980B9", + "#9B59B6", "#8E44AD", "#34495E", "#2C3E50", "#F1C40F", "#F39C12", + "#E67E22", "#D35400", "#E74C3C", "#C0392B", "#ECF0F1", "#BDC3C7", + "#95A5A6", "#7F8C8D", "#52B3D9", "#E8F8F5", "#D5F4E6", "#D6EAF8", + "#E8DAEF", "#FADBD8", "#F9E79F", "#FAD7A0", "#F5B7B1", "#D7DBDD", + } + case "Pastel": + return []string{ + "#FFB3BA", "#FFDFBA", "#FFFFBA", "#BAFFC9", "#BAE1FF", "#E0BBE4", + "#FFDFD3", "#FEC8D8", "#D5AAFF", "#B4F8C8", "#A0E7E5", "#FFAEBC", + "#FBE7C6", "#B4F8C8", "#A0C4FF", "#BDB2FF", "#FFC6FF", "#FDFFB6", + "#CAFFBF", "#9BF6FF", "#A0C4FF", "#BDB2FF", "#FFC6FF", "#FFFFFC", + "#FFD6A5", "#FDFFB6", "#CAFFBF", "#A8E6CF", "#FFD3B6", "#FFAAA5", + } + case "Nord": + return []string{ + "#2E3440", "#3B4252", "#434C5E", "#4C566A", "#D8DEE9", "#E5E9F0", + "#ECEFF4", "#8FBCBB", "#88C0D0", "#81A1C1", "#5E81AC", "#BF616A", + "#D08770", "#EBCB8B", "#A3BE8C", "#B48EAD", "#4C566A", "#434C5E", + "#3B4252", "#2E3440", "#ECEFF4", "#E5E9F0", "#D8DEE9", "#88C0D0", + } + case "Dracula": + return []string{ + "#282A36", "#44475A", "#F8F8F2", "#6272A4", "#8BE9FD", "#50FA7B", + "#FFB86C", "#FF79C6", "#BD93F9", "#FF5555", "#F1FA8C", "#21222C", + "#191A21", "#6272A4", "#B45BCF", "#4D4F68", "#626483", "#62D6E8", + "#EA51B2", "#EBFF87", "#00F769", "#B45BCF", "#7081D0", "#A1EFE4", + } + case "Solarized": + return []string{ + "#002B36", "#073642", "#586E75", "#657B83", "#839496", "#93A1A1", + "#EEE8D5", "#FDF6E3", "#B58900", "#CB4B16", "#DC322F", "#D33682", + "#6C71C4", "#268BD2", "#2AA198", "#859900", "#002B36", "#073642", + "#586E75", "#657B83", "#839496", "#93A1A1", "#EEE8D5", "#FDF6E3", + } + case "Gruvbox": + return []string{ + "#282828", "#CC241D", "#98971A", "#D79921", "#458588", "#B16286", + "#689D6A", "#A89984", "#928374", "#FB4934", "#B8BB26", "#FABD2F", + "#83A598", "#D3869B", "#8EC07C", "#EBDBB2", "#FBF1C7", "#3C3836", + "#504945", "#665C54", "#7C6F64", "#D65D0E", "#FE8019", "#BDAE93", + } + case "One Dark": + return []string{ + "#282C34", "#ABB2BF", "#E06C75", "#D19A66", "#E5C07B", "#98C379", + "#56B6C2", "#61AFEF", "#C678DD", "#BE5046", "#3B4048", "#4B5263", + "#545862", "#565C64", "#5C6370", "#636D83", "#828997", "#2C323C", + "#353B45", "#3E4451", "#4F5666", "#5F697A", "#6B7587", "#979EAB", + } + case "Monokai": return []string{ - "#2E3436", "#555753", "#888A85", "#BABDB6", "#D3D7CF", "#EEEEEC", - "#FCE94F", "#EDD400", "#C4A000", "#8AE234", "#73D216", "#4E9A06", - "#729FCF", "#3465A4", "#204A87", "#AD7FA8", "#75507B", "#5C3566", - "#EF2929", "#CC0000", "#A40000", "#FCAF3E", "#F57900", "#CE5C00", + "#272822", "#F8F8F2", "#F92672", "#E6DB74", "#A6E22E", "#66D9EF", + "#AE81FF", "#FD971F", "#75715E", "#49483E", "#3E3D32", "#F8F8F0", + "#F5F4F1", "#A59F85", "#FD5FF0", "#F4BF75", "#FFF59D", "#CFCFC2", + "#A1EFE4", "#FFE792", "#CC6633", "#778899", "#9D550F", "#E69F66", } - case "Visibone Core": + case "KiJiSH Dark Pastel Terminal": return []string{ - "#000000", "#333333", "#666666", "#999999", "#CCCCCC", "#FFFFFF", - "#FF0000", "#FF9900", "#FFFF00", "#00FF00", "#00FFFF", "#0000FF", - "#9900FF", "#FF00FF", "#FF0066", "#663300", "#CC6633", "#99CC33", - "#6699CC", "#CC33CC", "#CC9999", "#33CCCC", "#336699", "#9966CC", + "#2C2C2C", "#DCDCDC", "#3F3F3F", "#D67979", "#60B48A", "#DFAF8F", + "#9AB8D7", "#DC8CC3", "#8CD0D3", "#DCDCDC", "#709080", "#DCA3A3", + "#72D5A3", "#F0DFAF", "#94BFF3", "#EC93D3", "#93E0E3", "#FFFFFF", } default: vals := []int{0x00, 0x33, 0x66, 0x99, 0xCC, 0xFF} |
