diff --git a/src/app.rs b/src/app.rs index 70e17e0..7576cd8 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1360,6 +1360,14 @@ impl Application for App { return Command::none(); } + // Close gallery mode if open + if let Some(tab) = self.tab_model.data_mut::(entity) { + if tab.gallery { + tab.gallery = false; + return Command::none(); + } + } + // Close menus and context panes in order per message // Why: It'd be weird to close everything all at once // Usually, the Escape key (for example) closes menus and panes one by one instead @@ -2392,6 +2400,9 @@ impl Application for App { tab::Command::PreviewCancel => { self.preview_opt = None; } + tab::Command::WindowDrag => { + commands.push(window::drag(self.main_window_id())); + } } } return Command::batch(commands); @@ -2772,6 +2783,17 @@ impl Application for App { } fn dialog(&self) -> Option> { + //TODO: should gallery view just be a dialog? + let entity = self.tab_model.active(); + if let Some(tab) = self.tab_model.data::(entity) { + if tab.gallery { + return Some( + tab.gallery_view() + .map(move |tab_message| Message::TabMessage(Some(entity), tab_message)), + ); + } + } + let dialog_page = match self.dialog_pages.front() { Some(some) => some, None => return None, diff --git a/src/dialog.rs b/src/dialog.rs index a9faf74..85c800a 100644 --- a/src/dialog.rs +++ b/src/dialog.rs @@ -389,6 +389,62 @@ struct App { } impl App { + fn button_row(&self) -> Element { + let cosmic_theme::Spacing { space_xxs, .. } = theme::active().cosmic().spacing; + + let mut row = widget::row::with_capacity( + if !self.filters.is_empty() { 1 } else { 0 } + self.choices.len() * 2 + 3, + ) + .align_items(Alignment::Center) + .padding(space_xxs) + .spacing(space_xxs); + if !self.filters.is_empty() { + row = row.push(widget::dropdown( + &self.filters, + self.filter_selected, + Message::Filter, + )); + } + for (choice_i, choice) in self.choices.iter().enumerate() { + match choice { + DialogChoice::CheckBox { label, value, .. } => { + row = row.push(widget::checkbox(label, *value, move |checked| { + Message::Choice(choice_i, if checked { 1 } else { 0 }) + })); + } + DialogChoice::ComboBox { + label, + options, + selected, + .. + } => { + row = row.push(widget::text::heading(label)); + row = row.push(widget::dropdown(options, *selected, move |option_i| { + Message::Choice(choice_i, option_i) + })); + } + } + } + if let DialogKind::SaveFile { filename } = &self.flags.kind { + row = row.push( + widget::text_input("", filename) + .id(self.filename_id.clone()) + .on_input(Message::Filename) + .on_submit(Message::Save(false)), + ); + } else { + row = row.push(widget::horizontal_space(Length::Fill)); + } + row = row.push(widget::button::standard(fl!("cancel")).on_press(Message::Cancel)); + row = row.push(if self.flags.kind.save() { + widget::button::suggested(&self.accept_label).on_press(Message::Save(false)) + } else { + widget::button::suggested(&self.accept_label).on_press(Message::Open) + }); + + row.into() + } + fn preview(&self, kind: &PreviewKind) -> Element { let mut children = Vec::with_capacity(1); match kind { @@ -701,6 +757,21 @@ impl Application for App { } fn dialog(&self) -> Option> { + //TODO: should gallery view just be a dialog? + if self.tab.gallery { + return Some( + widget::column::with_children(vec![ + self.tab.gallery_view().map(Message::TabMessage), + // Draw button row as part of the overlay + widget::container(self.button_row()) + .width(Length::Fill) + .style(theme::Container::WindowBackground) + .into(), + ]) + .into(), + ); + } + let dialog_page = match self.dialog_pages.front() { Some(some) => some, None => return None, @@ -877,6 +948,12 @@ impl Application for App { } fn on_escape(&mut self) -> Command { + if self.tab.gallery { + // Close gallery if open + self.tab.gallery = false; + return Command::none(); + } + if self.search_active { // Close search if open self.search_active = false; @@ -1279,6 +1356,9 @@ impl Application for App { tab::Command::PreviewCancel => { self.preview_opt = None; } + tab::Command::WindowDrag => { + commands.push(window::drag(self.main_window_id())); + } unsupported => { log::warn!("{unsupported:?} not supported in dialog mode"); } @@ -1359,9 +1439,8 @@ impl Application for App { /// Creates a view after each update. fn view(&self) -> Element { - let cosmic_theme::Spacing { space_xxs, .. } = theme::active().cosmic().spacing; - let mut tab_column = widget::column::with_capacity(2); + tab_column = tab_column.push( //TODO: key binds for dialog self.tab @@ -1369,57 +1448,7 @@ impl Application for App { .map(move |message| Message::TabMessage(message)), ); - let mut row = widget::row::with_capacity( - if !self.filters.is_empty() { 1 } else { 0 } + self.choices.len() * 2 + 3, - ) - .align_items(Alignment::Center) - .padding(space_xxs) - .spacing(space_xxs); - if !self.filters.is_empty() { - row = row.push(widget::dropdown( - &self.filters, - self.filter_selected, - Message::Filter, - )); - } - for (choice_i, choice) in self.choices.iter().enumerate() { - match choice { - DialogChoice::CheckBox { label, value, .. } => { - row = row.push(widget::checkbox(label, *value, move |checked| { - Message::Choice(choice_i, if checked { 1 } else { 0 }) - })); - } - DialogChoice::ComboBox { - label, - options, - selected, - .. - } => { - row = row.push(widget::text::heading(label)); - row = row.push(widget::dropdown(options, *selected, move |option_i| { - Message::Choice(choice_i, option_i) - })); - } - } - } - if let DialogKind::SaveFile { filename } = &self.flags.kind { - row = row.push( - widget::text_input("", filename) - .id(self.filename_id.clone()) - .on_input(Message::Filename) - .on_submit(Message::Save(false)), - ); - } else { - row = row.push(widget::horizontal_space(Length::Fill)); - } - row = row.push(widget::button::standard(fl!("cancel")).on_press(Message::Cancel)); - row = row.push(if self.flags.kind.save() { - widget::button::suggested(&self.accept_label).on_press(Message::Save(false)) - } else { - widget::button::suggested(&self.accept_label).on_press(Message::Open) - }); - - tab_column = tab_column.push(row); + tab_column = tab_column.push(self.button_row()); let content: Element<_> = tab_column.into(); diff --git a/src/tab.rs b/src/tab.rs index f1d61bf..2c877c1 100644 --- a/src/tab.rs +++ b/src/tab.rs @@ -818,6 +818,7 @@ pub enum Command { OpenInNewWindow(PathBuf), Preview(PreviewKind, Duration), PreviewCancel, + WindowDrag, } #[derive(Clone, Debug)] @@ -837,6 +838,7 @@ pub enum Message { EditLocation(Option), OpenInNewTab(PathBuf), EmptyTrash, + Gallery(bool), GoNext, GoPrevious, ItemDown, @@ -861,6 +863,7 @@ pub enum Message { DndHover(Location), DndEnter(Location), DndLeave(Location), + WindowDrag, ZoomDefault, ZoomIn, ZoomOut, @@ -1042,20 +1045,21 @@ impl Item { widget::button::icon(widget::icon::from_name("go-next-symbolic")) .on_press(app::Message::TabMessage(None, Message::ItemRight)), ); - /* match self .thumbnail_opt .as_ref() .unwrap_or(&ItemThumbnail::NotImage) { - ItemThumbnail::NotImage => {} - _ => { - row = row.push(widget::button::icon(widget::icon::from_name( - "window-maximize-symbolic", - ))); + ItemThumbnail::Rgba(_, _) => { + if let Some(path) = self.path_opt() { + row = row.push( + widget::button::icon(widget::icon::from_name("view-fullscreen-symbolic")) + .on_press(app::Message::TabMessage(None, Message::Gallery(true))), + ); + } } + _ => {} } - */ column = column.push(row); column = column.push(widget::row::with_children(vec![ @@ -1268,6 +1272,7 @@ pub struct Tab { pub history_i: usize, pub history: Vec, pub config: TabConfig, + pub gallery: bool, pub(crate) items_opt: Option>, pub dnd_hovered: Option<(Location, Instant)>, scrollable_id: widget::Id, @@ -1315,6 +1320,7 @@ impl Tab { history_i: 0, history, config, + gallery: false, items_opt: None, scrollable_id: widget::Id::unique(), select_focus: None, @@ -1897,6 +1903,9 @@ impl Tab { Message::EmptyTrash => { commands.push(Command::EmptyTrash); } + Message::Gallery(gallery) => { + self.gallery = gallery; + } Message::GoNext => { if let Some(history_i) = self.history_i.checked_add(1) { if let Some(location) = self.history.get(history_i) { @@ -2267,6 +2276,9 @@ impl Tab { self.dnd_hovered = None; } } + Message::WindowDrag => { + commands.push(Command::WindowDrag); + } Message::ZoomDefault => match self.config.view { View::List => self.config.icon_sizes.list = 100.try_into().unwrap(), View::Grid => self.config.icon_sizes.grid = 100.try_into().unwrap(), @@ -2493,6 +2505,98 @@ impl Tab { .into() } + pub fn gallery_view(&self) -> Element { + let cosmic_theme::Spacing { + space_xxs, + space_m, + space_l, + .. + } = theme::active().cosmic().spacing; + + //TODO: display error messages when image not found? + let mut name_opt = None; + let mut image_opt = None; + if let Some(index) = self.select_focus { + if let Some(items) = &self.items_opt { + if let Some(item) = items.get(index) { + name_opt = Some(widget::text::heading(&item.display_name)); + match item + .thumbnail_opt + .as_ref() + .unwrap_or(&ItemThumbnail::NotImage) + { + ItemThumbnail::Rgba(_, _) => { + if let Some(path) = item.path_opt() { + image_opt = Some( + widget::image::viewer(widget::image::Handle::from_path(path)) + .min_scale(1.0) + .width(Length::Fill) + .height(Length::Fill), + ); + } + } + _ => {} + } + } + } + } + + let mut column = widget::column::with_capacity(2); + column = column.push(widget::vertical_space(Length::Fixed(space_xxs.into()))); + { + let mut row = widget::row::with_capacity(5).align_items(Alignment::Center); + row = row.push(widget::horizontal_space(Length::Fill)); + if let Some(name) = name_opt { + row = row.push(name); + } + row = row.push(widget::horizontal_space(Length::Fill)); + row = row.push( + widget::button::icon(widget::icon::from_name("window-close-symbolic")) + .on_press(Message::Gallery(false)), + ); + row = row.push(widget::horizontal_space(Length::Fixed(space_xxs.into()))); + // This mouse area provides window drag while the header bar is hidden + let mouse_area = mouse_area::MouseArea::new(row).on_press(|_| Message::WindowDrag); + column = column.push(mouse_area); + } + { + let mut row = widget::row::with_capacity(7).align_items(Alignment::Center); + row = row.push(widget::horizontal_space(Length::Fixed(space_m.into()))); + row = row.push( + widget::button::icon(widget::icon::from_name("go-previous-symbolic")) + .on_press(Message::ItemLeft), + ); + row = row.push(widget::horizontal_space(Length::Fixed(space_xxs.into()))); + if let Some(image) = image_opt { + row = row.push(image); + } else { + //TODO: what to do when no image? + row = row.push(widget::horizontal_space(Length::Fill)); + } + row = row.push(widget::horizontal_space(Length::Fixed(space_xxs.into()))); + row = row.push( + widget::button::icon(widget::icon::from_name("go-next-symbolic")) + .on_press(Message::ItemRight), + ); + row = row.push(widget::horizontal_space(Length::Fixed(space_m.into()))); + column = column.push(row); + } + + widget::container(column) + .width(Length::Fill) + .height(Length::Fill) + .style(theme::Container::Custom(Box::new(|theme| { + let cosmic = theme.cosmic(); + let mut bg = cosmic.bg_color(); + bg.alpha = 0.75; + widget::container::Appearance { + background: Some(Color::from(bg).into()), + ..Default::default() + } + }))) + .into() + } + pub fn location_view(&self) -> Element { //TODO: responsiveness is done in a hacky way, potentially move this to a custom widget? fn text_width<'a>( @@ -2590,7 +2694,7 @@ impl Tab { _ => {} } //TODO: make it possible to resize with the mouse - return crate::mouse_area::MouseArea::new(row) + return mouse_area::MouseArea::new(row) .on_press(move |_point_opt| Message::ToggleSort(msg)) .into(); };