diff --git a/Cargo.lock b/Cargo.lock index 5011439..03736b6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -74,6 +74,72 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "darling" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f2c43f534ea4b0b049015d00269734195e6d3f0f6635cb692251aca6f9f8b3c" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e91455b86830a1c21799d94524df0845183fa55bafd9aa137b01c7d1065fa36" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29b5acf0dea37a7f66f7b25d2c5e93fd46f8f6968b1a5d7a3e02e97768afc95a" +dependencies = [ + "darling_core", + "quote", + "syn", +] + +[[package]] +name = "derive_builder" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d13202debe11181040ae9063d739fa32cfcaaebe2275fe387703460ae2365b30" +dependencies = [ + "derive_builder_macro", +] + +[[package]] +name = "derive_builder_core" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66e616858f6187ed828df7c64a6d71720d83767a7f19740b2d1b6fe6327b36e5" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "derive_builder_macro" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58a94ace95092c5acb1e97a7e846b310cfbd499652f72297da7493f618a98d73" +dependencies = [ + "derive_builder_core", + "syn", +] + [[package]] name = "field-offset" version = "0.3.4" @@ -84,10 +150,17 @@ dependencies = [ "rustc_version", ] +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + [[package]] name = "focus-annotator" version = "0.1.0" dependencies = [ + "derive_builder", "gls", "gtk4", "libadwaita", @@ -441,6 +514,12 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + [[package]] name = "itoa" version = "1.0.1" @@ -806,6 +885,12 @@ version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ecab6c735a6bb4139c0caafd0cc3635748bbb3acf4550e8138122099251f309" +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + [[package]] name = "syn" version = "1.0.82" diff --git a/Cargo.toml b/Cargo.toml index b955ebb..afdd163 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,4 +11,4 @@ gtk = { version = "0.4", package = "gtk4" } gls = { version = "0.1" } serde = { version = "1.0", features = ["derive"]} serde_json = { version = "1.0" } - +derive_builder = "0.10" diff --git a/src/main.rs b/src/main.rs index 933c94c..ac4d123 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,109 +1,27 @@ +#[macro_use] +extern crate derive_builder; + mod state; +mod ui; mod constants; pub use crate::state::AnnotationImage; pub use crate::constants::MARGIN_BOTTOM; +use crate::state::AnnotationZStack; +pub use crate::ui::ImageUI; use std::cell::{RefCell}; +use std::fs; use std::sync::{Arc}; -use std::{fs}; -use adw::{prelude::*, ApplicationWindow, HeaderBar, SplitButton}; -use constants::{MARGIN_TOP, MARGIN_LEFT, MARGIN_RIGHT_SCALE_ADDITIONAL, TOGGLE_NEIGHBOURS_TEXT, TOGGLE_NEIGHBOURS_TEXT_TOGGLED, SCALE_STEP}; -use gio::SimpleAction; +use adw::{prelude::*, Application}; +use constants::{TOGGLE_NEIGHBOURS_TEXT_TOGGLED, TOGGLE_NEIGHBOURS_TEXT, SCALE_STEP}; use glib::clone; -use gtk::{gio, glib, FileChooserAction, FileChooserDialog, ResponseType}; -use gtk::{ - ActionBar, Application, AspectFrame, Box, Button, FileFilter, Grid, Image, Orientation, - PositionType, Scale, Separator, ToggleButton, -}; -use state::{AnnotationZStack, State}; +use gtk::gio::SimpleAction; +use gtk::{glib, FileChooserDialog, FileChooserAction, ResponseType, FileFilter}; -#[derive(Debug, Clone)] -struct ImageUI { - individual: Arc, - center: Arc, - neighbours: [Arc; 8], - focus_scale: Arc, -} - -impl ImageUI { - pub fn new() -> ImageUI { - let individual = Arc::new(Image::builder().vexpand(true).hexpand(true).build()); - let center = Arc::new(Image::builder().vexpand(true).hexpand(true).build()); - let neighbours = [ - Arc::new(Image::builder().vexpand(true).hexpand(true).build()), - Arc::new(Image::builder().vexpand(true).hexpand(true).build()), - Arc::new(Image::builder().vexpand(true).hexpand(true).build()), - Arc::new(Image::builder().vexpand(true).hexpand(true).build()), - Arc::new(Image::builder().vexpand(true).hexpand(true).build()), - Arc::new(Image::builder().vexpand(true).hexpand(true).build()), - Arc::new(Image::builder().vexpand(true).hexpand(true).build()), - Arc::new(Image::builder().vexpand(true).hexpand(true).build()), - ]; - let focus_scale = Arc::new( - Scale::builder() - .orientation(Orientation::Vertical) - .vexpand(true) - .margin_top(MARGIN_TOP) - .margin_bottom(MARGIN_BOTTOM) - .margin_start(MARGIN_LEFT) - .draw_value(true) - .inverted(true) - .round_digits(0) - .digits(0) - .build(), - ); - - ImageUI { - individual, - center, - neighbours, - focus_scale, - } - } - pub fn update(&self, state : &State) { - if let Some(annotation_image) = state.get_current_annotation_image() { - self.update_image(&annotation_image); - } - self.update_focus_scale(&state); - } - fn update_image(&self, annotation_image: &AnnotationImage) { - self.individual - .set_from_file(Some(annotation_image.image_path.clone())); - self.center - .set_from_file(Some(annotation_image.image_path.clone())); - - for index in 0..annotation_image.neighbours.len() { - self.neighbours[index].set_from_file(annotation_image.neighbours[index].clone()); - } - } - - fn update_focus_scale(&self, state: &State) { - let max = state.get_current_foucs_stack_max().unwrap_or(0) as f64; - self.focus_scale.set_range(0.0, max); - - if let Some(best_index) = state.get_current_foucs_stack_best_index() { - self.focus_scale.clear_marks(); - self.focus_scale.add_mark( - best_index as f64, - PositionType::Right, - Some("focus"), - ); - self.focus_scale.set_margin_end(0); - } else { - self.focus_scale.clear_marks(); - self.focus_scale.set_margin_end(MARGIN_RIGHT_SCALE_ADDITIONAL); - } - - if let Some(current_value) = state.image_index { - self.focus_scale.set_value(current_value as f64); - } else { - self.focus_scale.set_value(f64::floor(max / 2.0)); - } - } -} +use state::{State}; fn main() { let application = Application::builder() @@ -114,21 +32,12 @@ fn main() { adw::init(); }); - application.connect_startup(setup_shortcuts); + application.connect_startup(ImageUI::setup_shortcuts); application.connect_activate(build_ui); application.run(); } -fn setup_shortcuts(app: &Application) { - app.set_accels_for_action("win.toggle_neighbour", &["G"]); - app.set_accels_for_action("win.increment_focus_scale", &["W"]); - app.set_accels_for_action("win.decrement_focus_scale", &["S"]); - app.set_accels_for_action("win.mark_focus", &["M"]); - app.set_accels_for_action("win.skip_focus", &["N"]); - app.set_accels_for_action("win.back_focus", &["B"]); - -} fn build_ui(app: &Application) { @@ -138,52 +47,8 @@ fn build_ui(app: &Application) { // MAIN CONTENT // ////////////////// - let image_ui = Arc::new(ImageUI::new()); - image_ui.update(&(state.as_ref().borrow())); - - let focus_neighbours_grid = Arc::new( - Grid::builder() - .vexpand(true) - .hexpand(true) - .column_spacing(0) - .row_spacing(0) - .build(), - ); - - let focus_neighbours_aspect_frame = AspectFrame::builder() - .ratio(1.0) - .xalign(0.5) - .yalign(0.5) - .build(); - focus_neighbours_aspect_frame.set_child(Some(image_ui.individual.as_ref())); - - focus_neighbours_grid.attach(image_ui.center.as_ref(), 1, 1, 1, 1); - - for index in 0..image_ui.neighbours.len() { - // offset index for later images to leave out middle of the grid - let grid_index: i32 = if index > 3 { index + 1 } else { index } - .try_into() - .unwrap(); - let column = grid_index % 3; - let row = grid_index / 3; - focus_neighbours_grid.attach(image_ui.neighbours[index].as_ref(), column, row, 1, 1); - eprintln!("{column} {row}"); - } - - //let focus_scale = image_ui.focus_scale.clone(); - - // update_focus_scale(focus_scale.as_ref(), z_stack.clone()); - - let center_content_seperator = Separator::new(Orientation::Vertical); - let center_content = Box::builder() - //.hexpand(true) - .orientation(Orientation::Horizontal) - .spacing(0) - .build(); - - center_content.append(image_ui.focus_scale.as_ref()); - center_content.append(¢er_content_seperator); - center_content.append(&focus_neighbours_aspect_frame); + let image_ui = Arc::new(ImageUI::new(app)); + //image_ui.build_ui(); image_ui.focus_scale.connect_value_changed(clone!(@strong image_ui, @strong state => move |x| { let index = x.value() as usize; @@ -191,97 +56,39 @@ fn build_ui(app: &Application) { image_ui.update(&state.borrow()); })); - //////////// - // HEADER // - //////////// - - //let show_start_title_buttons = Button::new(); - let header_bar = HeaderBar::builder() - .title_widget(&adw::WindowTitle::new("First App", "")) - .build(); - - // TODO: add button functionality - let open_button = SplitButton::builder().label("Open").build(); - header_bar.pack_start(&open_button); - //////////////////// // BOTTOM TOOLBAR // /////////////////// - - let bottom_toolbar = ActionBar::builder().build(); - - let back_button = Button::builder().label("Back").build(); - - back_button.connect_clicked(|button| { + image_ui.back_button.connect_clicked(|button| { button.activate_action("win.back_focus", None) .expect("The action does not exist."); }); - let skip_button = Button::builder().label("Skip").build(); - - skip_button.connect_clicked(|button| { + image_ui.skip_button.connect_clicked(|button| { button.activate_action("win.skip_focus", None) .expect("The action does not exist."); }); - let focus_button = Button::builder() - .label("Set Focus") - .css_classes(vec!["suggested-action".to_string()]) - .build(); - - focus_button.connect_clicked(|button| { + image_ui.focus_button.connect_clicked(|button| { button.activate_action("win.mark_focus", None) .expect("The action does not exist."); }); - let focus_skip_link_widget = Box::builder() - .css_classes(vec!["linked".to_string()]) - .build(); - focus_skip_link_widget.append(&back_button); - focus_skip_link_widget.append(&skip_button); - focus_skip_link_widget.append(&focus_button); - - let neighbour_toggle_button = ToggleButton::builder() - .label(TOGGLE_NEIGHBOURS_TEXT) - .width_request(158) - .build(); let focus_image = image_ui.individual.clone(); - neighbour_toggle_button.connect_toggled( - clone!(@strong focus_neighbours_grid => move |x| match x.is_active() { + image_ui.neighbour_toggle_button.connect_toggled( + clone!(@strong image_ui => move |x| match x.is_active() { true => { - focus_neighbours_aspect_frame.set_child(Some(focus_neighbours_grid.as_ref())); + image_ui.focus_neighbours_aspect_frame.set_child(Some(image_ui.focus_neighbours_grid.as_ref())); x.set_label(TOGGLE_NEIGHBOURS_TEXT_TOGGLED); } false => { - focus_neighbours_aspect_frame.set_child(Some(focus_image.as_ref())); + image_ui.focus_neighbours_aspect_frame.set_child(Some(focus_image.as_ref())); x.set_label(TOGGLE_NEIGHBOURS_TEXT); } }), ); - bottom_toolbar.pack_start(&neighbour_toggle_button); - bottom_toolbar.pack_end(&focus_skip_link_widget); - ////////////////////// - // MAIN APPLICATION // - ////////////////////// - - // Combine the content in a box - let application_vertical_widget = Box::new(Orientation::Vertical, 0); - // Adwaitas' ApplicationWindow does not include a HeaderBar - - application_vertical_widget.append(&header_bar); - application_vertical_widget.append(¢er_content); - application_vertical_widget.append(&bottom_toolbar); - - let window = ApplicationWindow::builder() - .application(app) - .default_width(800) - .default_height(600) - // add content to window - .content(&application_vertical_widget) - .build(); - - open_button.connect_clicked(clone!(@weak window, @strong image_ui, @strong state => move |_| { + image_ui.open_button.connect_clicked(clone!(@strong image_ui, @strong state => move |_| { // TODO: actually open and load data @@ -290,11 +97,11 @@ fn build_ui(app: &Application) { let filter = FileFilter::new(); filter.add_pattern(r"*.json"); - let file_chooser = Arc::new(FileChooserDialog::new(Some("Chose a data file!"), Some(&window), file_chooser_action, &buttons)); + let file_chooser = Arc::new(FileChooserDialog::new(Some("Chose a data file!"), Some(image_ui.window.as_ref()), file_chooser_action, &buttons)); file_chooser.set_select_multiple(false); file_chooser.set_filter(&filter); - file_chooser.connect_response(clone!(@weak window, @strong image_ui, @weak state => move |dialog: &FileChooserDialog, response: ResponseType| { + file_chooser.connect_response(clone!(@strong image_ui, @weak state => move |dialog: &FileChooserDialog, response: ResponseType| { if response == ResponseType::Ok { let file = dialog.file().expect("Couldn't get file"); eprintln!("Open"); @@ -320,8 +127,8 @@ fn build_ui(app: &Application) { //////////////////////// let action_toggle_neighbour = SimpleAction::new("toggle_neighbour", None); - action_toggle_neighbour.connect_activate(clone!(@weak window => move |_, _| { - neighbour_toggle_button.set_active(!neighbour_toggle_button.is_active()); + action_toggle_neighbour.connect_activate(clone!(@strong image_ui => move |_, _| { + image_ui.neighbour_toggle_button.set_active(!image_ui.neighbour_toggle_button.is_active()); })); let action_focus_scale_increment = SimpleAction::new("increment_focus_scale", None); @@ -358,12 +165,11 @@ fn build_ui(app: &Application) { image_ui.update(&state); })); - window.add_action(&action_toggle_neighbour); - window.add_action(&action_focus_scale_increment); - window.add_action(&action_focus_scale_decrement); - window.add_action(&mark_focus); - window.add_action(&skip_focus); - window.add_action(&back_focus); - - window.show(); + image_ui.window.add_action(&action_toggle_neighbour); + image_ui.window.add_action(&action_focus_scale_increment); + image_ui.window.add_action(&action_focus_scale_decrement); + image_ui.window.add_action(&mark_focus); + image_ui.window.add_action(&skip_focus); + image_ui.window.add_action(&back_focus); + image_ui.show(); } \ No newline at end of file diff --git a/src/ui/mod.rs b/src/ui/mod.rs new file mode 100644 index 0000000..1444649 --- /dev/null +++ b/src/ui/mod.rs @@ -0,0 +1,245 @@ +use std::sync::Arc; + +use adw::{Application, ApplicationWindow, HeaderBar, SplitButton}; +use gtk::{ + traits::{BoxExt, GridExt, GtkApplicationExt, RangeExt, ScaleExt, WidgetExt}, + ActionBar, AspectFrame, Box, Button, Grid, Image, Orientation, PositionType, Scale, Separator, + ToggleButton, +}; + +use crate::{ + constants::{MARGIN_LEFT, MARGIN_RIGHT_SCALE_ADDITIONAL, MARGIN_TOP, TOGGLE_NEIGHBOURS_TEXT}, + state::State, + AnnotationImage, MARGIN_BOTTOM, +}; + +#[derive(Debug, Clone, Builder)] +pub struct ImageUI { + pub window: Arc, + pub application_vertical_widget: Arc, + pub individual: Arc, + pub center: Arc, + pub neighbours: [Arc; 8], + pub focus_scale: Arc, + pub focus_neighbours_grid: Arc, + pub focus_neighbours_aspect_frame: Arc, + + pub neighbour_toggle_button: Arc, + pub open_button: Arc, + pub back_button: Arc