Files
icon-color-tool/src/main.rs
T

261 lines
10 KiB
Rust

mod color_scheme;
mod composite_icon;
mod config;
mod icon_index;
mod ui;
use gtk::prelude::*;
use gtk::{glib, Application, ApplicationWindow, Stack};
use std::path::{Path, PathBuf};
use crate::color_scheme::ColorScheme;
use crate::composite_icon::CompositeIcon;
use std::cell::RefCell;
use std::rc::Rc;
const APP_ID: &str = "dev.rootiest.IconColorTool";
fn main() -> glib::ExitCode {
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);
}
};
let initial_arg = std::env::args().nth(1);
let initial_icon = initial_arg
.as_deref()
.map(|a| resolve_icon_arg(a, &breeze_path));
if let Some(Err(e)) = &initial_icon {
eprintln!("Error: {e}");
std::process::exit(1);
}
let window = ApplicationWindow::builder()
.application(app)
.title("Icon Color Tool")
.default_width(900)
.default_height(800)
.build();
let stack = Stack::new();
window.set_child(Some(&stack));
let current_composite: Rc<RefCell<Option<Rc<RefCell<CompositeIcon>>>>> = Rc::new(RefCell::new(None));
{
let current_composite = Rc::clone(&current_composite);
let window_clone = window.clone();
window.connect_close_request(move |_| {
if let Some(comp) = current_composite.borrow().as_ref() {
if comp.borrow().is_modified {
let dialog = gtk::AlertDialog::builder()
.message("You have unsaved changes. Are you sure you want to close?")
.detail("All unsaved changes will be lost.")
.buttons(["Cancel", "Discard"])
.cancel_button(0)
.default_button(1)
.build();
let window_clone2 = window_clone.clone();
let comp_ref = Rc::clone(&current_composite);
dialog.choose(Some(&window_clone), gtk::gio::Cancellable::NONE, move |res| {
if let Ok(1) = res {
if let Some(c) = comp_ref.borrow().as_ref() {
c.borrow_mut().is_modified = false;
}
window_clone2.close();
}
});
return glib::Propagation::Stop;
}
}
glib::Propagation::Proceed
});
}
build_picker(&stack, &window, breeze_path.clone(), Rc::clone(&current_composite));
if let Some(Ok(path)) = initial_icon {
build_editor(&stack, &window, path, breeze_path, Rc::clone(&current_composite));
}
window.present();
});
app.run()
}
fn resolve_icon_arg(arg: &str, breeze_path: &Path) -> anyhow::Result<PathBuf> {
let as_given = PathBuf::from(arg);
if as_given.is_absolute() {
anyhow::ensure!(
as_given.exists(),
"Icon not found at {}",
as_given.display()
);
return Ok(as_given);
}
if as_given.exists() {
return Ok(as_given.canonicalize()?);
}
let under_breeze = breeze_path.join(arg);
anyhow::ensure!(
under_breeze.exists(),
"Icon not found at {} or {}",
as_given.display(),
under_breeze.display()
);
Ok(under_breeze.canonicalize()?)
}
fn build_picker(stack: &Stack, window: &ApplicationWindow, breeze_path: PathBuf, current_composite: Rc<RefCell<Option<Rc<RefCell<CompositeIcon>>>>>) {
let stack_for_callback = stack.clone();
let window_for_callback = window.clone();
let breeze_for_editor = breeze_path.clone();
let comp_callback = Rc::clone(&current_composite);
let widget = ui::picker::build_picker_page(breeze_path, move |icon_path| {
build_editor(&stack_for_callback, &window_for_callback, icon_path, breeze_for_editor.clone(), Rc::clone(&comp_callback));
});
stack.add_named(&widget, Some("picker"));
stack.set_visible_child_name("picker");
}
fn build_editor(stack: &Stack, window: &ApplicationWindow, icon_path: PathBuf, breeze_path: PathBuf, current_composite: Rc<RefCell<Option<Rc<RefCell<CompositeIcon>>>>>) {
let mut scheme = match ColorScheme::load(&icon_path) {
Ok(s) => s,
Err(e) => {
eprintln!("Failed to open {}: {e:#}", icon_path.display());
return;
}
};
scheme.auto_save = false;
let composite = Rc::new(RefCell::new(CompositeIcon::new(scheme)));
*current_composite.borrow_mut() = Some(Rc::clone(&composite));
let editor_container = gtk::Box::new(gtk::Orientation::Vertical, 0);
let rebuild_editor = {
let composite = Rc::clone(&composite);
let stack = stack.clone();
let window = window.clone();
let editor_container = editor_container.clone();
let current_comp = Rc::clone(&current_composite);
Rc::new(move || {
while let Some(child) = editor_container.first_child() {
editor_container.remove(&child);
}
let stack_clone = stack.clone();
let stack_emblem = stack.clone();
let window_clone = window.clone();
let comp = Rc::clone(&composite);
let comp_for_back = Rc::clone(&comp);
let current_comp_for_back = Rc::clone(&current_comp);
let page = ui::editor::build_editor_page(
Rc::clone(&composite),
move || {
if comp_for_back.borrow().is_modified {
let dialog = gtk::AlertDialog::builder()
.message("You have unsaved changes. Are you sure you want to discard them?")
.detail("All unsaved changes will be lost.")
.buttons(["Cancel", "Discard"])
.cancel_button(0)
.default_button(1)
.build();
let stack_clone = stack_clone.clone();
let comp_ref = Rc::clone(&current_comp_for_back);
dialog.choose(Some(&window_clone), gtk::gio::Cancellable::NONE, move |res| {
if let Ok(1) = res {
*comp_ref.borrow_mut() = None;
stack_clone.set_visible_child_name("picker");
}
});
} else {
*current_comp_for_back.borrow_mut() = None;
stack_clone.set_visible_child_name("picker");
}
},
move || {
stack_emblem.set_visible_child_name("emblem_picker");
},
{
let window_clone = window.clone();
let comp = Rc::clone(&comp);
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("Save Composite Icon As...")
.filters(&filters)
.default_filter(&filter)
.build();
let c = Rc::clone(&comp);
dialog.save(Some(&window_clone), gtk::gio::Cancellable::NONE, move |res| {
if let Ok(file) = res {
if let Some(path) = file.path() {
let rendered = c.borrow().render();
if let Err(e) = std::fs::write(&path, rendered) {
eprintln!("Failed to save composite to {}: {e}", path.display());
} else {
c.borrow_mut().is_modified = false;
}
}
}
});
}
}
);
editor_container.append(&page);
})
};
rebuild_editor();
if let Some(existing) = stack.child_by_name("editor") {
stack.remove(&existing);
}
stack.add_named(&editor_container, Some("editor"));
let rebuild_editor_clone = Rc::clone(&rebuild_editor);
let stack_emblem_callback = stack.clone();
let comp_for_picker = Rc::clone(&composite);
let emblem_picker = ui::picker::build_picker_page(breeze_path, 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();
c.set_emblem(emblem_scheme);
c.is_modified = true;
drop(c);
rebuild_editor_clone();
stack_emblem_callback.set_visible_child_name("editor");
} else {
eprintln!("Failed to load emblem: {}", emblem_path.display());
}
});
if let Some(existing) = stack.child_by_name("emblem_picker") {
stack.remove(&existing);
}
stack.add_named(&emblem_picker, Some("emblem_picker"));
stack.set_visible_child_name("editor");
}