From ac93fc79d401f7cb9839737e5dd161e4ea7670ea Mon Sep 17 00:00:00 2001 From: Jeremy Soller Date: Fri, 5 Jan 2024 11:18:38 -0700 Subject: [PATCH] Add context menu --- Cargo.lock | 32 ++-- i18n/en/cosmic_files.ftl | 6 + src/main.rs | 75 +++++---- src/menu.rs | 60 +++++++ src/mime_icon.rs | 1 + src/mouse_area.rs | 330 +++++++++++++++++++++++++++++++++++++++ src/tab.rs | 23 +-- 7 files changed, 473 insertions(+), 54 deletions(-) create mode 100644 src/menu.rs create mode 100644 src/mouse_area.rs diff --git a/Cargo.lock b/Cargo.lock index c79d3a0..d8d6fc9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -920,7 +920,7 @@ dependencies = [ [[package]] name = "cosmic-config" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic.git#1bd39b17ae7c97934ea214028cd31c578b589e3e" +source = "git+https://github.com/pop-os/libcosmic.git#4674e4b23e80adcebb9096217dd3ff0a2c5ac15b" dependencies = [ "atomicwrites", "cosmic-config-derive", @@ -935,7 +935,7 @@ dependencies = [ [[package]] name = "cosmic-config-derive" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic.git#1bd39b17ae7c97934ea214028cd31c578b589e3e" +source = "git+https://github.com/pop-os/libcosmic.git#4674e4b23e80adcebb9096217dd3ff0a2c5ac15b" dependencies = [ "quote", "syn 1.0.109", @@ -984,7 +984,7 @@ dependencies = [ [[package]] name = "cosmic-theme" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic.git#1bd39b17ae7c97934ea214028cd31c578b589e3e" +source = "git+https://github.com/pop-os/libcosmic.git#4674e4b23e80adcebb9096217dd3ff0a2c5ac15b" dependencies = [ "almost", "cosmic-config", @@ -2273,7 +2273,7 @@ dependencies = [ [[package]] name = "iced" version = "0.12.0" -source = "git+https://github.com/pop-os/libcosmic.git#1bd39b17ae7c97934ea214028cd31c578b589e3e" +source = "git+https://github.com/pop-os/libcosmic.git#4674e4b23e80adcebb9096217dd3ff0a2c5ac15b" dependencies = [ "iced_accessibility", "iced_core", @@ -2288,7 +2288,7 @@ dependencies = [ [[package]] name = "iced_accessibility" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic.git#1bd39b17ae7c97934ea214028cd31c578b589e3e" +source = "git+https://github.com/pop-os/libcosmic.git#4674e4b23e80adcebb9096217dd3ff0a2c5ac15b" dependencies = [ "accesskit", "accesskit_winit", @@ -2297,7 +2297,7 @@ dependencies = [ [[package]] name = "iced_core" version = "0.12.0" -source = "git+https://github.com/pop-os/libcosmic.git#1bd39b17ae7c97934ea214028cd31c578b589e3e" +source = "git+https://github.com/pop-os/libcosmic.git#4674e4b23e80adcebb9096217dd3ff0a2c5ac15b" dependencies = [ "bitflags 1.3.2", "instant", @@ -2313,7 +2313,7 @@ dependencies = [ [[package]] name = "iced_futures" version = "0.12.0" -source = "git+https://github.com/pop-os/libcosmic.git#1bd39b17ae7c97934ea214028cd31c578b589e3e" +source = "git+https://github.com/pop-os/libcosmic.git#4674e4b23e80adcebb9096217dd3ff0a2c5ac15b" dependencies = [ "futures", "iced_core", @@ -2326,7 +2326,7 @@ dependencies = [ [[package]] name = "iced_graphics" version = "0.12.0" -source = "git+https://github.com/pop-os/libcosmic.git#1bd39b17ae7c97934ea214028cd31c578b589e3e" +source = "git+https://github.com/pop-os/libcosmic.git#4674e4b23e80adcebb9096217dd3ff0a2c5ac15b" dependencies = [ "bitflags 1.3.2", "bytemuck", @@ -2349,7 +2349,7 @@ dependencies = [ [[package]] name = "iced_renderer" version = "0.12.0" -source = "git+https://github.com/pop-os/libcosmic.git#1bd39b17ae7c97934ea214028cd31c578b589e3e" +source = "git+https://github.com/pop-os/libcosmic.git#4674e4b23e80adcebb9096217dd3ff0a2c5ac15b" dependencies = [ "iced_graphics", "iced_tiny_skia", @@ -2362,7 +2362,7 @@ dependencies = [ [[package]] name = "iced_runtime" version = "0.12.0" -source = "git+https://github.com/pop-os/libcosmic.git#1bd39b17ae7c97934ea214028cd31c578b589e3e" +source = "git+https://github.com/pop-os/libcosmic.git#4674e4b23e80adcebb9096217dd3ff0a2c5ac15b" dependencies = [ "iced_core", "iced_futures", @@ -2372,7 +2372,7 @@ dependencies = [ [[package]] name = "iced_style" version = "0.12.0" -source = "git+https://github.com/pop-os/libcosmic.git#1bd39b17ae7c97934ea214028cd31c578b589e3e" +source = "git+https://github.com/pop-os/libcosmic.git#4674e4b23e80adcebb9096217dd3ff0a2c5ac15b" dependencies = [ "iced_core", "once_cell", @@ -2382,7 +2382,7 @@ dependencies = [ [[package]] name = "iced_tiny_skia" version = "0.12.0" -source = "git+https://github.com/pop-os/libcosmic.git#1bd39b17ae7c97934ea214028cd31c578b589e3e" +source = "git+https://github.com/pop-os/libcosmic.git#4674e4b23e80adcebb9096217dd3ff0a2c5ac15b" dependencies = [ "bytemuck", "cosmic-text", @@ -2400,7 +2400,7 @@ dependencies = [ [[package]] name = "iced_wgpu" version = "0.12.0" -source = "git+https://github.com/pop-os/libcosmic.git#1bd39b17ae7c97934ea214028cd31c578b589e3e" +source = "git+https://github.com/pop-os/libcosmic.git#4674e4b23e80adcebb9096217dd3ff0a2c5ac15b" dependencies = [ "bitflags 1.3.2", "bytemuck", @@ -2420,7 +2420,7 @@ dependencies = [ [[package]] name = "iced_widget" version = "0.12.0" -source = "git+https://github.com/pop-os/libcosmic.git#1bd39b17ae7c97934ea214028cd31c578b589e3e" +source = "git+https://github.com/pop-os/libcosmic.git#4674e4b23e80adcebb9096217dd3ff0a2c5ac15b" dependencies = [ "iced_renderer", "iced_runtime", @@ -2434,7 +2434,7 @@ dependencies = [ [[package]] name = "iced_winit" version = "0.12.0" -source = "git+https://github.com/pop-os/libcosmic.git#1bd39b17ae7c97934ea214028cd31c578b589e3e" +source = "git+https://github.com/pop-os/libcosmic.git#4674e4b23e80adcebb9096217dd3ff0a2c5ac15b" dependencies = [ "iced_graphics", "iced_runtime", @@ -2731,7 +2731,7 @@ checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4" [[package]] name = "libcosmic" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic.git#1bd39b17ae7c97934ea214028cd31c578b589e3e" +source = "git+https://github.com/pop-os/libcosmic.git#4674e4b23e80adcebb9096217dd3ff0a2c5ac15b" dependencies = [ "apply", "ashpd", diff --git a/i18n/en/cosmic_files.ftl b/i18n/en/cosmic_files.ftl index e852c52..89651bc 100644 --- a/i18n/en/cosmic_files.ftl +++ b/i18n/en/cosmic_files.ftl @@ -12,3 +12,9 @@ theme = Theme match-desktop = Match desktop dark = Dark light = Light + +# Context menu +copy = Copy +paste = Paste +select-all = Select all +new-tab = New tab diff --git a/src/main.rs b/src/main.rs index cf1917a..d063636 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,11 +10,15 @@ use cosmic::{ widget::{self, segmented_button}, Application, ApplicationExt, Element, }; -use std::{any::TypeId, env, path::PathBuf, process}; +use std::{any::TypeId, env, path::PathBuf, process, time::Instant}; use config::{AppTheme, Config, CONFIG_VERSION}; mod config; +mod menu; + +mod mouse_area; + mod localize; mod mime_icon; @@ -95,11 +99,9 @@ pub struct Flags { #[derive(Clone, Copy, Debug)] pub enum Action { - /*TODO Copy, Paste, SelectAll, - */ Settings, TabNew, } @@ -107,11 +109,9 @@ pub enum Action { impl Action { pub fn message(self, entity: segmented_button::Entity) -> Message { match self { - /*TODO Action::Copy => Message::Copy(Some(entity)), Action::Paste => Message::Paste(Some(entity)), Action::SelectAll => Message::SelectAll(Some(entity)), - */ Action::Settings => Message::ToggleContextPage(ContextPage::Settings), Action::TabNew => Message::TabNew, } @@ -124,6 +124,9 @@ pub enum Message { Todo, AppTheme(AppTheme), Config(Config), + Copy(Option), + Paste(Option), + SelectAll(Option), SystemThemeModeChange(cosmic_theme::ThemeMode), TabActivate(segmented_button::Entity), TabClose(segmented_button::Entity), @@ -313,6 +316,23 @@ impl Application for App { return self.update_config(); } } + Message::Copy(entity_opt) => { + log::warn!("TODO: COPY"); + } + Message::Paste(entity_opt) => { + log::warn!("TODO: PASTE"); + } + Message::SelectAll(entity_opt) => { + let entity = entity_opt.unwrap_or_else(|| self.tab_model.active()); + if let Some(tab) = self.tab_model.data_mut::(entity) { + if let Some(ref mut items) = tab.items_opt { + let select_time = Instant::now(); + for item in items.iter_mut() { + item.select_time = Some(select_time); + } + } + } + } Message::SystemThemeModeChange(_theme_mode) => { return self.update_config(); } @@ -479,32 +499,29 @@ impl Application for App { let entity = self.tab_model.active(); match self.tab_model.data::(entity) { Some(tab) => { - tab_column = tab_column.push( + let mut mouse_area = mouse_area::MouseArea::new( tab.view(self.core()) .map(move |message| Message::TabMessage(entity, message)), - ); - - /*TODO - let terminal_box = terminal_box(terminal).on_context_menu(move |position_opt| { - Message::TabContextMenu(entity, position_opt) - }); - - let context_menu = { - let terminal = terminal.lock().unwrap(); - terminal.context_menu - }; - - let tab_element: Element<'_, Message> = match tab.context_menu { - Some(position) => widget::popover( - terminal_box.context_menu(position), - menu::context_menu(&self.config, entity), - ) - .position(position) - .into(), - None => terminal_box.into(), - }; - tab_column = tab_column.push(tab_element); - */ + ) + .on_press(move |_point_opt| Message::TabMessage(entity, tab::Message::Click(None))); + if tab.context_menu.is_some() { + mouse_area = mouse_area + .on_right_press(move |_point_opt| Message::TabContextMenu(entity, None)); + } else { + mouse_area = mouse_area.on_right_press(move |point_opt| { + Message::TabContextMenu(entity, point_opt) + }); + } + let mut popover = widget::popover(mouse_area, menu::context_menu(entity)); + match tab.context_menu { + Some(point) => { + popover = popover.position(point); + } + None => { + popover = popover.show_popup(false); + } + } + tab_column = tab_column.push(popover); } None => { //TODO diff --git a/src/menu.rs b/src/menu.rs new file mode 100644 index 0000000..434fdf8 --- /dev/null +++ b/src/menu.rs @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: GPL-3.0-only + +use cosmic::{ + //TODO: export in cosmic::widget + iced::{ + widget::{column, horizontal_rule}, + Alignment, Background, Length, + }, + theme, + widget::{self, segmented_button}, + Element, +}; + +use crate::{fl, Action, Config, Message}; + +macro_rules! menu_button { + ($($x:expr),+ $(,)?) => ( + widget::button( + widget::Row::with_children( + vec![$(Element::from($x)),+] + ) + .align_items(Alignment::Center) + ) + .height(Length::Fixed(32.0)) + .padding([4, 16]) + .width(Length::Fill) + .style(theme::Button::MenuItem) + ); +} + +pub fn context_menu<'a>(entity: segmented_button::Entity) -> Element<'a, Message> { + let menu_action = |label, action| { + menu_button!(widget::text(label)).on_press(Message::TabContextAction(entity, action)) + }; + + widget::container(column!( + menu_action(fl!("copy"), Action::Copy), + menu_action(fl!("paste"), Action::Paste), + menu_action(fl!("select-all"), Action::SelectAll), + horizontal_rule(1), + menu_action(fl!("new-tab"), Action::TabNew), + menu_action(fl!("settings"), Action::Settings), + )) + .padding(1) + //TODO: move style to libcosmic + .style(theme::Container::custom(|theme| { + let cosmic = theme.cosmic(); + let component = &cosmic.background.component; + widget::container::Appearance { + icon_color: Some(component.on.into()), + text_color: Some(component.on.into()), + background: Some(Background::Color(component.base.into())), + border_radius: 8.0.into(), + border_width: 1.0, + border_color: component.divider.into(), + } + })) + .width(Length::Fixed(240.0)) + .into() +} diff --git a/src/mime_icon.rs b/src/mime_icon.rs index a9b13f1..1b029e6 100644 --- a/src/mime_icon.rs +++ b/src/mime_icon.rs @@ -7,6 +7,7 @@ pub const FALLBACK_MIME_ICON: &str = "text-x-generic"; #[derive(Debug, Eq, Hash, PartialEq)] struct MimeIconKey { + //TODO: this stores icon data for every path, instead store per mime type path: String, size: u16, } diff --git a/src/mouse_area.rs b/src/mouse_area.rs new file mode 100644 index 0000000..d54c8bf --- /dev/null +++ b/src/mouse_area.rs @@ -0,0 +1,330 @@ +//! A container for capturing mouse events. + +use cosmic::iced_core::{ + event::{self, Event}, + layout, mouse, overlay, renderer, touch, + widget::{tree, Operation, OperationOutputWrapper, Tree}, + {Clipboard, Element, Layout, Length, Point, Rectangle, Shell, Widget}, +}; + +/// Emit messages on mouse events. +#[allow(missing_debug_implementations)] +pub struct MouseArea<'a, Message, Renderer> { + content: Element<'a, Message, Renderer>, + on_drag: Option) -> Message + 'a>>, + on_press: Option) -> Message + 'a>>, + on_release: Option) -> Message + 'a>>, + on_right_press: Option) -> Message + 'a>>, + on_right_release: Option) -> Message + 'a>>, + on_middle_press: Option) -> Message + 'a>>, + on_middle_release: Option) -> Message + 'a>>, +} + +impl<'a, Message, Renderer> MouseArea<'a, Message, Renderer> { + /// The message to emit when a drag is initiated. + #[must_use] + pub fn on_drag(mut self, message: impl Fn(Option) -> Message + 'a) -> Self { + self.on_drag = Some(Box::new(message)); + self + } + + /// The message to emit on a left button press. + #[must_use] + pub fn on_press(mut self, message: impl Fn(Option) -> Message + 'a) -> Self { + self.on_press = Some(Box::new(message)); + self + } + + /// The message to emit on a left button release. + #[must_use] + pub fn on_release(mut self, message: impl Fn(Option) -> Message + 'a) -> Self { + self.on_release = Some(Box::new(message)); + self + } + + /// The message to emit on a right button press. + #[must_use] + pub fn on_right_press(mut self, message: impl Fn(Option) -> Message + 'a) -> Self { + self.on_right_press = Some(Box::new(message)); + self + } + + /// The message to emit on a right button release. + #[must_use] + pub fn on_right_release(mut self, message: impl Fn(Option) -> Message + 'a) -> Self { + self.on_right_release = Some(Box::new(message)); + self + } + + /// The message to emit on a middle button press. + #[must_use] + pub fn on_middle_press(mut self, message: impl Fn(Option) -> Message + 'a) -> Self { + self.on_middle_press = Some(Box::new(message)); + self + } + + /// The message to emit on a middle button release. + #[must_use] + pub fn on_middle_release(mut self, message: impl Fn(Option) -> Message + 'a) -> Self { + self.on_middle_release = Some(Box::new(message)); + self + } +} + +/// Local state of the [`MouseArea`]. +#[derive(Default)] +struct State { + // TODO: Support on_mouse_enter and on_mouse_exit + drag_initiated: Option, +} + +impl<'a, Message, Renderer> MouseArea<'a, Message, Renderer> { + /// Creates a [`MouseArea`] with the given content. + pub fn new(content: impl Into>) -> Self { + MouseArea { + content: content.into(), + on_drag: None, + on_press: None, + on_release: None, + on_right_press: None, + on_right_release: None, + on_middle_press: None, + on_middle_release: None, + } + } +} + +impl<'a, Message, Renderer> Widget for MouseArea<'a, Message, Renderer> +where + Renderer: renderer::Renderer, + Message: Clone, +{ + fn tag(&self) -> tree::Tag { + tree::Tag::of::() + } + + fn state(&self) -> tree::State { + tree::State::new(State::default()) + } + + fn children(&self) -> Vec { + vec![Tree::new(&self.content)] + } + + fn diff(&mut self, tree: &mut Tree) { + tree.diff_children(std::slice::from_mut(&mut self.content)); + } + + fn width(&self) -> Length { + self.content.as_widget().width() + } + + fn height(&self) -> Length { + self.content.as_widget().height() + } + + fn layout( + &self, + tree: &mut Tree, + renderer: &Renderer, + limits: &layout::Limits, + ) -> layout::Node { + self.content + .as_widget() + .layout(&mut tree.children[0], renderer, limits) + } + + fn operate( + &self, + tree: &mut Tree, + layout: Layout<'_>, + renderer: &Renderer, + operation: &mut dyn Operation>, + ) { + self.content + .as_widget() + .operate(&mut tree.children[0], layout, renderer, operation); + } + + fn on_event( + &mut self, + tree: &mut Tree, + event: Event, + layout: Layout<'_>, + cursor: mouse::Cursor, + renderer: &Renderer, + clipboard: &mut dyn Clipboard, + shell: &mut Shell<'_, Message>, + viewport: &Rectangle, + ) -> event::Status { + if let event::Status::Captured = self.content.as_widget_mut().on_event( + &mut tree.children[0], + event.clone(), + layout, + cursor, + renderer, + clipboard, + shell, + viewport, + ) { + return event::Status::Captured; + } + + update( + self, + &event, + layout, + cursor, + shell, + tree.state.downcast_mut::(), + ) + } + + fn mouse_interaction( + &self, + tree: &Tree, + layout: Layout<'_>, + cursor: mouse::Cursor, + viewport: &Rectangle, + renderer: &Renderer, + ) -> mouse::Interaction { + self.content.as_widget().mouse_interaction( + &tree.children[0], + layout, + cursor, + viewport, + renderer, + ) + } + + fn draw( + &self, + tree: &Tree, + renderer: &mut Renderer, + theme: &Renderer::Theme, + renderer_style: &renderer::Style, + layout: Layout<'_>, + cursor: mouse::Cursor, + viewport: &Rectangle, + ) { + self.content.as_widget().draw( + &tree.children[0], + renderer, + theme, + renderer_style, + layout, + cursor, + viewport, + ); + } + + fn overlay<'b>( + &'b mut self, + tree: &'b mut Tree, + layout: Layout<'_>, + renderer: &Renderer, + ) -> Option> { + self.content + .as_widget_mut() + .overlay(&mut tree.children[0], layout, renderer) + } +} + +impl<'a, Message, Renderer> From> + for Element<'a, Message, Renderer> +where + Message: 'a + Clone, + Renderer: 'a + renderer::Renderer, +{ + fn from(area: MouseArea<'a, Message, Renderer>) -> Element<'a, Message, Renderer> { + Element::new(area) + } +} + +/// Processes the given [`Event`] and updates the [`State`] of an [`MouseArea`] +/// accordingly. +fn update( + widget: &mut MouseArea<'_, Message, Renderer>, + event: &Event, + layout: Layout<'_>, + cursor: mouse::Cursor, + shell: &mut Shell<'_, Message>, + state: &mut State, +) -> event::Status { + if !cursor.is_over(layout.bounds()) { + return event::Status::Ignored; + } + + if let Some(message) = widget.on_press.as_ref() { + if let Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) + | Event::Touch(touch::Event::FingerPressed { .. }) = event + { + state.drag_initiated = cursor.position(); + shell.publish(message(cursor.position_in(layout.bounds()))); + + return event::Status::Captured; + } + } + + if let Some(message) = widget.on_release.as_ref() { + if let Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) + | Event::Touch(touch::Event::FingerLifted { .. }) = event + { + state.drag_initiated = None; + shell.publish(message(cursor.position_in(layout.bounds()))); + + return event::Status::Captured; + } + } + + if let Some(message) = widget.on_right_press.as_ref() { + if let Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Right)) = event { + shell.publish(message(cursor.position_in(layout.bounds()))); + + return event::Status::Captured; + } + } + + if let Some(message) = widget.on_right_release.as_ref() { + if let Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Right)) = event { + shell.publish(message(cursor.position_in(layout.bounds()))); + + return event::Status::Captured; + } + } + + if let Some(message) = widget.on_middle_press.as_ref() { + if let Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Middle)) = event { + shell.publish(message(cursor.position_in(layout.bounds()))); + + return event::Status::Captured; + } + } + + if let Some(message) = widget.on_middle_release.as_ref() { + if let Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Middle)) = event { + shell.publish(message(cursor.position_in(layout.bounds()))); + + return event::Status::Captured; + } + } + + if state.drag_initiated.is_none() && widget.on_drag.is_some() { + if let Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) + | Event::Touch(touch::Event::FingerPressed { .. }) = event + { + state.drag_initiated = cursor.position(); + } + } else if let Some((message, drag_source)) = widget.on_drag.as_ref().zip(state.drag_initiated) { + if let Some(position) = cursor.position() { + if position.distance(drag_source) > 1.0 { + state.drag_initiated = None; + shell.publish(message(cursor.position_in(layout.bounds()))); + + return event::Status::Captured; + } + } + } + + event::Status::Ignored +} diff --git a/src/tab.rs b/src/tab.rs index b02c3a5..8ea2270 100644 --- a/src/tab.rs +++ b/src/tab.rs @@ -208,7 +208,7 @@ pub fn rescan(tab_path: PathBuf) -> Vec { #[derive(Clone, Copy, Debug)] pub enum Message { - Click(usize), + Click(Option), Home, Parent, } @@ -276,10 +276,10 @@ impl Tab { pub fn update(&mut self, message: Message) -> bool { let mut cd = None; match message { - Message::Click(click_i) => { + Message::Click(click_i_opt) => { if let Some(ref mut items) = self.items_opt { for (i, item) in items.iter_mut().enumerate() { - if i == click_i { + if Some(i) == click_i_opt { if let Some(select_time) = item.select_time { if select_time.elapsed() < DOUBLE_CLICK_DURATION { if item.is_dir { @@ -306,6 +306,7 @@ impl Tab { } } } + self.context_menu = None; } Message::Home => { cd = Some(crate::home_dir()); @@ -380,7 +381,7 @@ impl Tab { .width(Length::Fixed(128.0)), ) .style(button_style(item.select_time.is_some())) - .on_press(Message::Click(i)) + .on_press(Message::Click(Some(i))) .into(), ); count += 1; @@ -420,7 +421,7 @@ impl Tab { ) .style(button_style(item.select_time.is_some())) .width(Length::Fill) - .on_press(Message::Click(i)) + .on_press(Message::Click(Some(i))) .into(), ); count += 1; @@ -436,10 +437,14 @@ impl Tab { } pub fn view(&self, core: &Core) -> Element { - widget::scrollable(match self.view { - View::Grid => self.grid_view(core), - View::List => self.list_view(core), - }) + widget::container( + widget::scrollable(match self.view { + View::Grid => self.grid_view(core), + View::List => self.list_view(core), + }) + .width(Length::Fill), + ) + .height(Length::Fill) .width(Length::Fill) .into() }