use vtcol::{Palette, Rgb, Scheme}; use std::rc::Rc; use anyhow::{anyhow, Result}; use slint::{Color, VecModel}; slint::slint! { import { HorizontalBox, VerticalBox } from "std-widgets.slint"; export global Aux := { property selected: 0; callback select(int); callback set-palette-color(int, color); callback format-rgb-hex(color) -> string; callback format-rgb-component(int) -> string; callback color-of-rgb-component(string, int) -> color; callback component-of-rgb(string, color) -> int; property mode: "normal"; /* normal | insert | command */ } Colors := Rectangle { width : 100%; border-width : 2px; background: @linear-gradient(90deg, #002b36 0%, #073642 100%); property <[color]> colors: [ rgb( 0, 0, 0), ]; property base: 0; squares := HorizontalBox { width : 100%; height : 20px; for col[i] in colors : psquare := Rectangle { property current-color : col; width : (squares.width / 8) - 12px; height : (squares.width / 8) - 12px; border-color : i == (Aux.selected - base) ? #cb4b16 : #839496; border-width : 3px; forward-focus: pval; ptouch := TouchArea { clicked => { Aux.select(base + i); } } prect := Rectangle { y : 3px; x : 3px; width : psquare.width - 6px; height : psquare.height - 6px; background : current-color; VerticalBox { Rectangle { pdesc := Text { /* Text will be set through callback from Rust. */ text : i; } } Rectangle { background : ptouch.has-hover ? #ffffff77 : #cccccc33; pval := Text { text : Aux.format-rgb-hex(current-color); font-family : "mono"; font-size : 9pt; } } Rectangle { background : green; } } } } } } ComponentEdit := HorizontalBox { height : 18pt; alignment : start; property val; property comp : ""; Text { width : 15%; height : 14.4pt; color : #a0a0a0; font-size : 12pt; font-weight : 700; text : Aux.format-rgb-component (val); } Rectangle { border-width : 2px; height : 14.4pt; width : 75%; background : Aux.color-of-rgb-component (comp, val); } } Edit := Rectangle { width : 100%; background : @linear-gradient(90deg, #002b36 0%, #073642 100%); border-color : Aux.mode == "insert" ? #cb4b16 : #839496; border-width : 3px; property rgb : Colors.white; callback update (color); update (col) => { debug("edit > update"); red.val = Aux.component-of-rgb("r", col); green.val = Aux.component-of-rgb("g", col); blue.val = Aux.component-of-rgb("b", col); } VerticalBox { alignment : start; red := ComponentEdit { val : Aux.component-of-rgb("r", rgb); comp: "r"; } green := ComponentEdit { val : Aux.component-of-rgb("g", rgb); comp: "g"; } blue := ComponentEdit { val : Aux.component-of-rgb("b", rgb); comp: "b"; } } } CommandBar := Rectangle { width : 100%; height : t.preferred-height + 3pt; background : @linear-gradient(90deg, #002b36 0%, #073642 100%); property text <=> t.text; t:= TextInput { text: ":"; color: #fdf6e3; vertical-alignment: center; } } GuiEdit := Window { property scheme-name <=> name.text; callback set-primary ([color]); callback set-secondary ([color]); set-primary (colors) => { primary-colors .colors = colors; } set-secondary (colors) => { secondary-colors.colors = colors; } callback get-palette-color(int) -> color; callback set-palette-color(int, color); callback update-edit <=> edit.update; callback user-quit(); key-inputs := FocusScope { key-pressed(event) => { debug("input: got", event.text, "shift?", event.modifiers.shift); /* Escape always gets you back to normal mode. */ if (event.text == Keys.Escape) { debug("enter normal"); master.enter-normal-mode(); } else if (Aux.mode == "command") { if (event.text == Keys.Return) { // TODO: execute typed command master.enter-normal-mode(); } } else if (Aux.mode == "insert") { /* In insert mode, most selection bindings are frozen. */ } /* Insert mode. */ else { /* Normal mode bindings. */ if (event.text == "q") { user-quit(); } if (event.text == "h") { debug("select prev"); master.select-prev(); } else if (event.text == "l") { debug("select next"); master.select-next(); } else if (event.text == " ") { if (event.modifiers.shift) { debug("select prev"); master.select-prev(); } else { debug("select next"); master.select-next(); } } else if (event.text == "j" || event.text == "k") { debug("select other row"); master.select-other-row(); } else if (event.text == Keys.Return || event.text == "i") { debug("enter insert"); master.enter-insert-mode(); } else if (event.text == ":") { debug("enter command"); master.enter-command-mode(); } if (event.modifiers.control) { //debug("control was pressed during this event"); } } /* Normal mode. */ edit.update( Aux.selected < 8 ? primary-colors.colors [Aux.selected] : secondary-colors.colors [Aux.selected - 8] ); accept } } get-palette-color (i) => { i < 8 ? primary-colors.colors [i] : secondary-colors.colors [i - 8] } set-palette-color (i, col) => { if (i < 8) { primary-colors.colors [i] = col; } else { secondary-colors.colors [i - 8] = col; } } master := VerticalLayout { height : 100%; callback select-prev(); callback select-next(); callback select-other-row(); callback enter-command-mode(); callback enter-insert-mode(); callback enter-normal-mode(); select-prev () => { Aux.select (Aux.selected == 0 ? 15 : Aux.selected - 1); debug ("selected previous, now", Aux.selected); } select-next () => { Aux.select (mod (Aux.selected + 1, 16)); debug ("selected next, now", Aux.selected); } select-other-row () => { Aux.select (mod (Aux.selected + 8, 16)); debug ("selected row above/below, now", Aux.selected); } enter-command-mode () => { Aux.mode = "command"; } enter-normal-mode () => { Aux.mode = "normal" ; } enter-insert-mode () => { Aux.mode = "insert" ; } status := HorizontalBox { width : 100%; name := Text { text : ""; color : #a0a0a0; font-weight : 700; } } primary-colors := Colors { base : 0; } secondary-colors := Colors { base : 8; } edit := Edit { } /* invisible spacer */ Rectangle { vertical-stretch : 2; width : 100%; min-height : 1pt; //background : #ffFFffFF; background : #aaaaaaaa; } command := CommandBar { } } } } pub struct Edit { name: Option, scheme: Scheme, } impl Edit { pub fn new(name: Option, scheme: Scheme) -> Self { Self { name, scheme } } pub fn run(self) -> Result<()> { let Self { name, scheme } = self; let pal = Palette::try_from(&scheme)?.iter().collect::>(); let primary = pal[0..8] .iter() .map(|Rgb(r, g, b)| Color::from_rgb_u8(*r, *g, *b)) .collect::>(); let secondary = pal[8..] .iter() .map(|Rgb(r, g, b)| Color::from_rgb_u8(*r, *g, *b)) .collect::>(); let primary = Rc::new(VecModel::from(primary)); let secondary = Rc::new(VecModel::from(secondary)); let gui = GuiEdit::new(); gui.on_user_quit(move || { std::process::exit(0); }); { let guiw = gui.as_weak(); let npal = pal.len(); gui.global::().on_select(move |i: i32| { let i = (i as usize % npal) as i32; guiw.unwrap().global::().set_selected(i); let col = guiw.unwrap().invoke_get_palette_color(i); guiw.unwrap().invoke_update_edit(col); }); } { let guiw = gui.as_weak(); let npal = pal.len(); gui.global::().on_set_palette_color(move |i, col| { let i = (i as usize % npal) as i32; guiw.unwrap().invoke_set_palette_color(i, col); }); } gui.global::().on_format_rgb_hex(|col| { let x = (col.red() as u32) << 2 | (col.green() as u32) << 1 | (col.blue() as u32); format!("#{:06x}", x).into() }); gui.global::().on_format_rgb_component(|val| { let val = 0xff & val; format!("#{:02x} ({})", val, val).into() }); gui.global::().on_color_of_rgb_component(|comp, val| { match comp.as_str() { "r" => Color::from_rgb_u8(val as u8, 0, 0), "g" => Color::from_rgb_u8(0, val as u8, 0), "b" => Color::from_rgb_u8(0, 0, val as u8), _ => Color::from_rgb_u8(0, 0, 0), } }); /* Why the hell is there no API for this in .60‽ */ gui.global::().on_component_of_rgb(|comp, col| { match comp.as_str() { "r" => col.red() as i32, "g" => col.green() as i32, "b" => col.blue() as i32, _ => 0, } }); if let Some(name) = name { gui.set_scheme_name(name.into()); } gui.invoke_set_primary(primary.into()); gui.invoke_set_secondary(secondary.into()); gui.run(); Ok(()) } }