diff --git a/README.md b/README.md index 3e7d7f7..88d5b34 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,8 @@ embedded in [Breeze icon](https://github.com/KDE/breeze-icons) SVGs. ```sh icon-color-tool # opens a searchable icon picker +icon-color-tool -h # print colorful help output +icon-color-tool -d path/to/icons # use a custom icon directory icon-color-tool path/to/icon.svg # opens directly in the color editor ``` diff --git a/src/composite_icon.rs b/src/composite_icon.rs index 9eb026a..b602975 100644 --- a/src/composite_icon.rs +++ b/src/composite_icon.rs @@ -63,7 +63,7 @@ impl CompositeIcon { if let Some(emblem) = &self.emblem { fn parse_dims(svg: &str) -> (f64, f64) { - if let Some(re) = Regex::new(r#"(?i)]*viewBox\s*=\s*"([^"]+)""#).ok() { + if let Ok(re) = Regex::new(r#"(?i)]*viewBox\s*=\s*"([^"]+)""#) { if let Some(caps) = re.captures(svg) { let parts: Vec<&str> = caps.get(1).unwrap().as_str().split_whitespace().collect(); if parts.len() == 4 { @@ -75,14 +75,14 @@ impl CompositeIcon { } let mut w = 64.0; let mut h = 64.0; - if let Some(re_w) = Regex::new(r#"(?i)]*width\s*=\s*"([^"a-zA-Z]+)[a-zA-Z]*""#).ok() { + if let Ok(re_w) = Regex::new(r#"(?i)]*width\s*=\s*"([^"a-zA-Z]+)[a-zA-Z]*""#) { if let Some(caps) = re_w.captures(svg) { if let Ok(val) = caps.get(1).unwrap().as_str().trim().parse::() { w = val; } } } - if let Some(re_h) = Regex::new(r#"(?i)]*height\s*=\s*"([^"a-zA-Z]+)[a-zA-Z]*""#).ok() { + if let Ok(re_h) = Regex::new(r#"(?i)]*height\s*=\s*"([^"a-zA-Z]+)[a-zA-Z]*""#) { if let Some(caps) = re_h.captures(svg) { if let Ok(val) = caps.get(1).unwrap().as_str().trim().parse::() { h = val; diff --git a/src/icon_index.rs b/src/icon_index.rs index af17255..47f20a9 100644 --- a/src/icon_index.rs +++ b/src/icon_index.rs @@ -37,11 +37,11 @@ pub fn list_svg_icons(root: &Path) -> Vec { let size_str = if let Some(c) = components.next() { c.as_os_str().to_string_lossy().into_owned() - } else { continue; }; + } else { String::new() }; let category = if let Some(c) = components.next() { c.as_os_str().to_string_lossy().into_owned() - } else { continue; }; + } else { String::new() }; let score = if size_str == "scalable" { 10000 @@ -75,8 +75,8 @@ pub fn fuzzy_match_score(query: &str, candidate: &str) -> Option { let mut q_char = q_chars.next().unwrap(); let mut match_indices = Vec::new(); - let mut chars = candidate_lower.char_indices(); - while let Some((i, c)) = chars.next() { + let chars = candidate_lower.char_indices(); + for (i, c) in chars { if c == q_char { match_indices.push(i); if let Some(next_q) = q_chars.next() { diff --git a/src/main.rs b/src/main.rs index 796f5f9..d074129 100644 --- a/src/main.rs +++ b/src/main.rs @@ -31,18 +31,52 @@ use std::rc::Rc; const APP_ID: &str = "dev.rootiest.IconColorTool"; fn main() -> glib::ExitCode { + let mut args: Vec = std::env::args().collect(); + if args.iter().any(|a| a == "-h" || a == "--help") { + println!("\x1b[1;36micon-color-tool\x1b[0m - Edit the ColorScheme-* CSS colors in KDE Breeze icons\n"); + println!("\x1b[1;33mUSAGE:\x1b[0m"); + println!(" icon-color-tool [OPTIONS] [FILE]\n"); + println!("\x1b[1;33mARGS:\x1b[0m"); + println!(" \x1b[32m\x1b[0m Open a specific SVG file directly in the color editor\n"); + println!("\x1b[1;33mOPTIONS:\x1b[0m"); + println!(" \x1b[32m-d, --icon-dir \x1b[0m Specify a custom directory to source icons from"); + println!(" \x1b[32m-h, --help\x1b[0m Print help information"); + std::process::exit(0); + } + + let mut custom_icon_dir = None; + let mut i = 1; + while i < args.len() { + if (args[i] == "--icon-dir" || args[i] == "-d") && i + 1 < args.len() { + custom_icon_dir = Some(PathBuf::from(&args[i + 1])); + args.remove(i); + args.remove(i); + continue; + } + i += 1; + } + + let initial_arg = args.get(1).cloned(); let app = Application::builder().application_id(APP_ID).build(); - app.connect_activate(|app| { - let breeze_path = match config::load_breeze_path() { - Ok(p) => p, - Err(e) => { - eprintln!("Failed to resolve breeze-icons path: {e:#}"); - std::process::exit(1); + app.connect_activate(move |app| { + let breeze_path = match &custom_icon_dir { + Some(p) => { + if !p.exists() { + eprintln!("Error: Icon directory {} does not exist", p.display()); + std::process::exit(1); + } + p.clone() } + None => match config::load_breeze_path() { + Ok(p) => p, + Err(e) => { + eprintln!("Failed to resolve breeze-icons path: {e:#}"); + std::process::exit(1); + } + }, }; - let initial_arg = std::env::args().nth(1); let initial_icon = initial_arg .as_deref() .map(|a| resolve_icon_arg(a, &breeze_path)); @@ -104,7 +138,7 @@ fn main() -> glib::ExitCode { window.present(); }); - app.run() + app.run_with_args(&args) } fn resolve_icon_arg(arg: &str, breeze_path: &Path) -> anyhow::Result { @@ -136,8 +170,9 @@ fn build_picker(stack: &Stack, window: &ApplicationWindow, breeze_path: PathBuf, let breeze_for_editor = breeze_path.clone(); let comp_callback = Rc::clone(¤t_composite); - let widget = ui::picker::build_picker_page(breeze_path, "folder.svg", move |icon_path| { - build_editor(&stack_for_callback, &window_for_callback, icon_path, breeze_for_editor.clone(), Rc::clone(&comp_callback)); + let window_for_closure = window_for_callback.clone(); + let widget = ui::picker::build_picker_page(&window_for_callback, breeze_path, "folder.svg", move |icon_path| { + build_editor(&stack_for_callback, &window_for_closure, icon_path, breeze_for_editor.clone(), Rc::clone(&comp_callback)); }); stack.add_named(&widget, Some("picker")); stack.set_visible_child_name("picker"); @@ -253,7 +288,7 @@ fn build_editor(stack: &Stack, window: &ApplicationWindow, icon_path: PathBuf, b let stack_emblem_callback = stack.clone(); let comp_for_picker = Rc::clone(&composite); - let emblem_picker = ui::picker::build_picker_page(breeze_path, "symbolic", move |emblem_path| { + let emblem_picker = ui::picker::build_picker_page(window, breeze_path, "symbolic", move |emblem_path| { if let Ok(mut emblem_scheme) = ColorScheme::load_with_namespace(&emblem_path, "Emblem") { emblem_scheme.auto_save = false; let mut c = comp_for_picker.borrow_mut(); diff --git a/src/ui/editor.rs b/src/ui/editor.rs index 6e2ebfb..906a032 100644 --- a/src/ui/editor.rs +++ b/src/ui/editor.rs @@ -240,9 +240,12 @@ pub fn build_editor_page( } let popover = gtk::Popover::new(); + #[allow(deprecated)] let chooser = gtk::ColorChooserWidget::new(); + #[allow(deprecated)] chooser.set_use_alpha(false); - chooser.set_rgba(&*current_rgba.borrow()); + #[allow(deprecated)] + chooser.set_rgba(¤t_rgba.borrow()); popover.set_child(Some(&chooser)); let button = gtk::MenuButton::builder() @@ -257,7 +260,9 @@ pub fn build_editor_page( let current_rgba = Rc::clone(¤t_rgba); let drawing_area = drawing_area.clone(); + #[allow(deprecated)] chooser.connect_rgba_notify(move |c| { + #[allow(deprecated)] let new_rgba = c.rgba(); let new_hex = format!( "#{:02x}{:02x}{:02x}", @@ -267,12 +272,11 @@ pub fn build_editor_page( ); let changed = { let mut comp = composite.borrow_mut(); - let res; - if is_emblem { - res = comp.emblem.as_mut().unwrap().update_color(&tag, &new_hex).unwrap_or(false); + let res = if is_emblem { + comp.emblem.as_mut().unwrap().update_color(&tag, &new_hex).unwrap_or(false) } else { - res = comp.bg.update_color(&tag, &new_hex).unwrap_or(false); - } + comp.bg.update_color(&tag, &new_hex).unwrap_or(false) + }; if res { comp.is_modified = true; } diff --git a/src/ui/picker.rs b/src/ui/picker.rs index 6a3579b..3be9b89 100644 --- a/src/ui/picker.rs +++ b/src/ui/picker.rs @@ -24,10 +24,13 @@ use std::rc::Rc; const RESULT_LIMIT: usize = 300; pub fn build_picker_page( + window: >k::ApplicationWindow, breeze_root: PathBuf, initial_query: &str, on_select: impl Fn(PathBuf) + 'static, ) -> gtk::Widget { + let on_select = Rc::new(on_select) as Rc; + let root = GtkBox::new(Orientation::Vertical, 8); root.set_margin_top(8); root.set_margin_bottom(8); @@ -39,7 +42,42 @@ pub fn build_picker_page( search.set_text(initial_query); } search.set_placeholder_text(Some("Search icons…")); - root.append(&search); + search.set_hexpand(true); + + let open_button = gtk::Button::with_label("Open SVG…"); + { + let window_clone = window.clone(); + let on_select_clone = Rc::clone(&on_select); + open_button.connect_clicked(move |_| { + let filter = gtk::FileFilter::new(); + filter.set_name(Some("SVG Images")); + filter.add_mime_type("image/svg+xml"); + filter.add_pattern("*.svg"); + + let filters = gtk::gio::ListStore::new::(); + filters.append(&filter); + + let dialog = gtk::FileDialog::builder() + .title("Open SVG") + .filters(&filters) + .default_filter(&filter) + .build(); + + let on_select_clone2 = Rc::clone(&on_select_clone); + dialog.open(Some(&window_clone), gtk::gio::Cancellable::NONE, move |res| { + if let Ok(file) = res { + if let Some(path) = file.path() { + on_select_clone2(path); + } + } + }); + }); + } + + let search_box = GtkBox::new(Orientation::Horizontal, 8); + search_box.append(&search); + search_box.append(&open_button); + root.append(&search_box); let flow_box = FlowBox::new(); flow_box.set_valign(gtk::Align::Start); @@ -63,13 +101,14 @@ pub fn build_picker_page( { let current_matches = Rc::clone(¤t_matches); + let on_select_clone = Rc::clone(&on_select); flow_box.connect_child_activated(move |_, child| { let index = child.index(); if index < 0 { return; } if let Some(path) = current_matches.borrow().get(index as usize) { - on_select(path.clone()); + on_select_clone(path.clone()); } }); }