Compare commits
4
Commits
release-960c729
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4145029ed4
|
||
|
|
77399f938a | ||
|
|
2906bdad03 | ||
|
|
a4dfef304a
|
+24
@@ -1,3 +1,5 @@
|
|||||||
|
#### Submodules
|
||||||
|
breeze-icons/
|
||||||
|
|
||||||
# ──────────────── Added by agents-init ──────────────────
|
# ──────────────── Added by agents-init ──────────────────
|
||||||
# agents-init --agents
|
# agents-init --agents
|
||||||
@@ -14,3 +16,25 @@ docs/specs
|
|||||||
docs/devlogs
|
docs/devlogs
|
||||||
# ────────────────────────────────────────────────────────
|
# ────────────────────────────────────────────────────────
|
||||||
/target
|
/target
|
||||||
|
|
||||||
|
# id: gi-patterns-b05185fcb02652ad80fb49aa015b2ae8
|
||||||
|
# Created by https://www.toptal.com/developers/gitignore/api/rust
|
||||||
|
# Edit at https://www.toptal.com/developers/gitignore?templates=rust
|
||||||
|
|
||||||
|
### Rust ###
|
||||||
|
# Generated by Cargo
|
||||||
|
# will have compiled files and executables
|
||||||
|
debug/
|
||||||
|
target/
|
||||||
|
|
||||||
|
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
|
||||||
|
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
|
||||||
|
Cargo.lock
|
||||||
|
|
||||||
|
# These are backup files generated by rustfmt
|
||||||
|
**/*.rs.bk
|
||||||
|
|
||||||
|
# MSVC Windows builds of rustc generate these, which store debugging information
|
||||||
|
*.pdb
|
||||||
|
|
||||||
|
# End of https://www.toptal.com/developers/gitignore/api/rust
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ embedded in [Breeze icon](https://github.com/KDE/breeze-icons) SVGs.
|
|||||||
|
|
||||||
```sh
|
```sh
|
||||||
icon-color-tool # opens a searchable icon picker
|
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
|
icon-color-tool path/to/icon.svg # opens directly in the color editor
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
-1
Submodule breeze-icons deleted from f8d528544f
@@ -63,7 +63,7 @@ impl CompositeIcon {
|
|||||||
if let Some(emblem) = &self.emblem {
|
if let Some(emblem) = &self.emblem {
|
||||||
|
|
||||||
fn parse_dims(svg: &str) -> (f64, f64) {
|
fn parse_dims(svg: &str) -> (f64, f64) {
|
||||||
if let Some(re) = Regex::new(r#"(?i)<svg[^>]*viewBox\s*=\s*"([^"]+)""#).ok() {
|
if let Ok(re) = Regex::new(r#"(?i)<svg[^>]*viewBox\s*=\s*"([^"]+)""#) {
|
||||||
if let Some(caps) = re.captures(svg) {
|
if let Some(caps) = re.captures(svg) {
|
||||||
let parts: Vec<&str> = caps.get(1).unwrap().as_str().split_whitespace().collect();
|
let parts: Vec<&str> = caps.get(1).unwrap().as_str().split_whitespace().collect();
|
||||||
if parts.len() == 4 {
|
if parts.len() == 4 {
|
||||||
@@ -75,14 +75,14 @@ impl CompositeIcon {
|
|||||||
}
|
}
|
||||||
let mut w = 64.0;
|
let mut w = 64.0;
|
||||||
let mut h = 64.0;
|
let mut h = 64.0;
|
||||||
if let Some(re_w) = Regex::new(r#"(?i)<svg[^>]*width\s*=\s*"([^"a-zA-Z]+)[a-zA-Z]*""#).ok() {
|
if let Ok(re_w) = Regex::new(r#"(?i)<svg[^>]*width\s*=\s*"([^"a-zA-Z]+)[a-zA-Z]*""#) {
|
||||||
if let Some(caps) = re_w.captures(svg) {
|
if let Some(caps) = re_w.captures(svg) {
|
||||||
if let Ok(val) = caps.get(1).unwrap().as_str().trim().parse::<f64>() {
|
if let Ok(val) = caps.get(1).unwrap().as_str().trim().parse::<f64>() {
|
||||||
w = val;
|
w = val;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let Some(re_h) = Regex::new(r#"(?i)<svg[^>]*height\s*=\s*"([^"a-zA-Z]+)[a-zA-Z]*""#).ok() {
|
if let Ok(re_h) = Regex::new(r#"(?i)<svg[^>]*height\s*=\s*"([^"a-zA-Z]+)[a-zA-Z]*""#) {
|
||||||
if let Some(caps) = re_h.captures(svg) {
|
if let Some(caps) = re_h.captures(svg) {
|
||||||
if let Ok(val) = caps.get(1).unwrap().as_str().trim().parse::<f64>() {
|
if let Ok(val) = caps.get(1).unwrap().as_str().trim().parse::<f64>() {
|
||||||
h = val;
|
h = val;
|
||||||
|
|||||||
+4
-4
@@ -37,11 +37,11 @@ pub fn list_svg_icons(root: &Path) -> Vec<PathBuf> {
|
|||||||
|
|
||||||
let size_str = if let Some(c) = components.next() {
|
let size_str = if let Some(c) = components.next() {
|
||||||
c.as_os_str().to_string_lossy().into_owned()
|
c.as_os_str().to_string_lossy().into_owned()
|
||||||
} else { continue; };
|
} else { String::new() };
|
||||||
|
|
||||||
let category = if let Some(c) = components.next() {
|
let category = if let Some(c) = components.next() {
|
||||||
c.as_os_str().to_string_lossy().into_owned()
|
c.as_os_str().to_string_lossy().into_owned()
|
||||||
} else { continue; };
|
} else { String::new() };
|
||||||
|
|
||||||
let score = if size_str == "scalable" {
|
let score = if size_str == "scalable" {
|
||||||
10000
|
10000
|
||||||
@@ -75,8 +75,8 @@ pub fn fuzzy_match_score(query: &str, candidate: &str) -> Option<i32> {
|
|||||||
let mut q_char = q_chars.next().unwrap();
|
let mut q_char = q_chars.next().unwrap();
|
||||||
let mut match_indices = Vec::new();
|
let mut match_indices = Vec::new();
|
||||||
|
|
||||||
let mut chars = candidate_lower.char_indices();
|
let chars = candidate_lower.char_indices();
|
||||||
while let Some((i, c)) = chars.next() {
|
for (i, c) in chars {
|
||||||
if c == q_char {
|
if c == q_char {
|
||||||
match_indices.push(i);
|
match_indices.push(i);
|
||||||
if let Some(next_q) = q_chars.next() {
|
if let Some(next_q) = q_chars.next() {
|
||||||
|
|||||||
+46
-11
@@ -31,18 +31,52 @@ use std::rc::Rc;
|
|||||||
const APP_ID: &str = "dev.rootiest.IconColorTool";
|
const APP_ID: &str = "dev.rootiest.IconColorTool";
|
||||||
|
|
||||||
fn main() -> glib::ExitCode {
|
fn main() -> glib::ExitCode {
|
||||||
|
let mut args: Vec<String> = 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<FILE>\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 <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();
|
let app = Application::builder().application_id(APP_ID).build();
|
||||||
|
|
||||||
app.connect_activate(|app| {
|
app.connect_activate(move |app| {
|
||||||
let breeze_path = match config::load_breeze_path() {
|
let breeze_path = match &custom_icon_dir {
|
||||||
Ok(p) => p,
|
Some(p) => {
|
||||||
Err(e) => {
|
if !p.exists() {
|
||||||
eprintln!("Failed to resolve breeze-icons path: {e:#}");
|
eprintln!("Error: Icon directory {} does not exist", p.display());
|
||||||
std::process::exit(1);
|
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
|
let initial_icon = initial_arg
|
||||||
.as_deref()
|
.as_deref()
|
||||||
.map(|a| resolve_icon_arg(a, &breeze_path));
|
.map(|a| resolve_icon_arg(a, &breeze_path));
|
||||||
@@ -104,7 +138,7 @@ fn main() -> glib::ExitCode {
|
|||||||
window.present();
|
window.present();
|
||||||
});
|
});
|
||||||
|
|
||||||
app.run()
|
app.run_with_args(&args)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn resolve_icon_arg(arg: &str, breeze_path: &Path) -> anyhow::Result<PathBuf> {
|
fn resolve_icon_arg(arg: &str, breeze_path: &Path) -> anyhow::Result<PathBuf> {
|
||||||
@@ -136,8 +170,9 @@ fn build_picker(stack: &Stack, window: &ApplicationWindow, breeze_path: PathBuf,
|
|||||||
let breeze_for_editor = breeze_path.clone();
|
let breeze_for_editor = breeze_path.clone();
|
||||||
let comp_callback = Rc::clone(¤t_composite);
|
let comp_callback = Rc::clone(¤t_composite);
|
||||||
|
|
||||||
let widget = ui::picker::build_picker_page(breeze_path, "folder.svg", move |icon_path| {
|
let window_for_closure = window_for_callback.clone();
|
||||||
build_editor(&stack_for_callback, &window_for_callback, icon_path, breeze_for_editor.clone(), Rc::clone(&comp_callback));
|
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.add_named(&widget, Some("picker"));
|
||||||
stack.set_visible_child_name("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 stack_emblem_callback = stack.clone();
|
||||||
let comp_for_picker = Rc::clone(&composite);
|
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") {
|
if let Ok(mut emblem_scheme) = ColorScheme::load_with_namespace(&emblem_path, "Emblem") {
|
||||||
emblem_scheme.auto_save = false;
|
emblem_scheme.auto_save = false;
|
||||||
let mut c = comp_for_picker.borrow_mut();
|
let mut c = comp_for_picker.borrow_mut();
|
||||||
|
|||||||
+10
-6
@@ -240,9 +240,12 @@ pub fn build_editor_page(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let popover = gtk::Popover::new();
|
let popover = gtk::Popover::new();
|
||||||
|
#[allow(deprecated)]
|
||||||
let chooser = gtk::ColorChooserWidget::new();
|
let chooser = gtk::ColorChooserWidget::new();
|
||||||
|
#[allow(deprecated)]
|
||||||
chooser.set_use_alpha(false);
|
chooser.set_use_alpha(false);
|
||||||
chooser.set_rgba(&*current_rgba.borrow());
|
#[allow(deprecated)]
|
||||||
|
chooser.set_rgba(¤t_rgba.borrow());
|
||||||
popover.set_child(Some(&chooser));
|
popover.set_child(Some(&chooser));
|
||||||
|
|
||||||
let button = gtk::MenuButton::builder()
|
let button = gtk::MenuButton::builder()
|
||||||
@@ -257,7 +260,9 @@ pub fn build_editor_page(
|
|||||||
let current_rgba = Rc::clone(¤t_rgba);
|
let current_rgba = Rc::clone(¤t_rgba);
|
||||||
let drawing_area = drawing_area.clone();
|
let drawing_area = drawing_area.clone();
|
||||||
|
|
||||||
|
#[allow(deprecated)]
|
||||||
chooser.connect_rgba_notify(move |c| {
|
chooser.connect_rgba_notify(move |c| {
|
||||||
|
#[allow(deprecated)]
|
||||||
let new_rgba = c.rgba();
|
let new_rgba = c.rgba();
|
||||||
let new_hex = format!(
|
let new_hex = format!(
|
||||||
"#{:02x}{:02x}{:02x}",
|
"#{:02x}{:02x}{:02x}",
|
||||||
@@ -267,12 +272,11 @@ pub fn build_editor_page(
|
|||||||
);
|
);
|
||||||
let changed = {
|
let changed = {
|
||||||
let mut comp = composite.borrow_mut();
|
let mut comp = composite.borrow_mut();
|
||||||
let res;
|
let res = if is_emblem {
|
||||||
if is_emblem {
|
comp.emblem.as_mut().unwrap().update_color(&tag, &new_hex).unwrap_or(false)
|
||||||
res = comp.emblem.as_mut().unwrap().update_color(&tag, &new_hex).unwrap_or(false);
|
|
||||||
} else {
|
} else {
|
||||||
res = comp.bg.update_color(&tag, &new_hex).unwrap_or(false);
|
comp.bg.update_color(&tag, &new_hex).unwrap_or(false)
|
||||||
}
|
};
|
||||||
if res {
|
if res {
|
||||||
comp.is_modified = true;
|
comp.is_modified = true;
|
||||||
}
|
}
|
||||||
|
|||||||
+41
-2
@@ -24,10 +24,13 @@ use std::rc::Rc;
|
|||||||
const RESULT_LIMIT: usize = 300;
|
const RESULT_LIMIT: usize = 300;
|
||||||
|
|
||||||
pub fn build_picker_page(
|
pub fn build_picker_page(
|
||||||
|
window: >k::ApplicationWindow,
|
||||||
breeze_root: PathBuf,
|
breeze_root: PathBuf,
|
||||||
initial_query: &str,
|
initial_query: &str,
|
||||||
on_select: impl Fn(PathBuf) + 'static,
|
on_select: impl Fn(PathBuf) + 'static,
|
||||||
) -> gtk::Widget {
|
) -> gtk::Widget {
|
||||||
|
let on_select = Rc::new(on_select) as Rc<dyn Fn(PathBuf)>;
|
||||||
|
|
||||||
let root = GtkBox::new(Orientation::Vertical, 8);
|
let root = GtkBox::new(Orientation::Vertical, 8);
|
||||||
root.set_margin_top(8);
|
root.set_margin_top(8);
|
||||||
root.set_margin_bottom(8);
|
root.set_margin_bottom(8);
|
||||||
@@ -39,7 +42,42 @@ pub fn build_picker_page(
|
|||||||
search.set_text(initial_query);
|
search.set_text(initial_query);
|
||||||
}
|
}
|
||||||
search.set_placeholder_text(Some("Search icons…"));
|
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::<gtk::FileFilter>();
|
||||||
|
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();
|
let flow_box = FlowBox::new();
|
||||||
flow_box.set_valign(gtk::Align::Start);
|
flow_box.set_valign(gtk::Align::Start);
|
||||||
@@ -63,13 +101,14 @@ pub fn build_picker_page(
|
|||||||
|
|
||||||
{
|
{
|
||||||
let current_matches = Rc::clone(¤t_matches);
|
let current_matches = Rc::clone(¤t_matches);
|
||||||
|
let on_select_clone = Rc::clone(&on_select);
|
||||||
flow_box.connect_child_activated(move |_, child| {
|
flow_box.connect_child_activated(move |_, child| {
|
||||||
let index = child.index();
|
let index = child.index();
|
||||||
if index < 0 {
|
if index < 0 {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if let Some(path) = current_matches.borrow().get(index as usize) {
|
if let Some(path) = current_matches.borrow().get(index as usize) {
|
||||||
on_select(path.clone());
|
on_select_clone(path.clone());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user