From daa074fd43526169c1a544a385a2360b9f847a76 Mon Sep 17 00:00:00 2001 From: sawyer bristol Date: Tue, 8 Oct 2024 17:08:11 -0600 Subject: [PATCH 1/9] woriking on custom selection list --- src/helpers.rs | 19 +- src/widgets/command_window.rs | 98 +++-- .../command_window/categorical_list.rs | 323 ++++++++++++++ .../categorical_selection_list.rs | 394 ++++++++++++++++++ src/widgets/command_window/results.rs | 52 +++ src/widgets/mod.rs | 23 +- 6 files changed, 850 insertions(+), 59 deletions(-) create mode 100644 src/widgets/command_window/categorical_list.rs create mode 100644 src/widgets/command_window/categorical_selection_list.rs create mode 100644 src/widgets/command_window/results.rs diff --git a/src/helpers.rs b/src/helpers.rs index 9fd9993..4bb6f32 100644 --- a/src/helpers.rs +++ b/src/helpers.rs @@ -4,7 +4,7 @@ use iced::widget::{ Button, }; pub use iced_fonts::BOOTSTRAP_FONT_BYTES; -use std::{borrow::Cow, str::FromStr}; +use std::{borrow::Cow, fmt, str::FromStr}; use url::{ParseError, Url}; use super::{Message, PixelFormat}; @@ -82,12 +82,13 @@ pub fn to_url(url: &str) -> Option { pub type Bookmarks = Vec; -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub struct Bookmark { url: Url, name: String, // icon: Optional<> } + impl Bookmark { pub fn new(url: &str, name: &str) -> Self { Bookmark { @@ -99,4 +100,18 @@ impl Bookmark { pub fn as_button(&self) -> Button { button(self.name.as_str()).on_press(Message::GoToUrl(self.url.to_string())) } + + pub fn url(&self) -> &Url { + &self.url + } + + pub fn name(&self) -> &String { + &self.name + } +} + +impl fmt::Display for Bookmark { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.name) + } } diff --git a/src/widgets/command_window.rs b/src/widgets/command_window.rs index 40af61f..8dc7e03 100644 --- a/src/widgets/command_window.rs +++ b/src/widgets/command_window.rs @@ -1,70 +1,80 @@ use iced::widget::{center, column, container, mouse_area, opaque, stack, text_input}; -use iced::{border, Color, Element, Font, Length, Theme}; -use iced_aw::SelectionList; -use strum::IntoEnumIterator; +use iced::{border, Color, Element, Length, Theme}; use super::Message; +use crate::Bookmark; -// pub enum ResultType { -// Command(Message), -// // Bookmark, -// } +mod results; +pub use results::{results_list, ResultType}; pub struct CommandWindowState { pub query: String, - commands: Vec, - pub selected_action: String, - pub selected_index: usize, + pub possible_results: Vec, + pub filtered_results: Vec, + pub selected_item: Option, } impl CommandWindowState { - pub fn new() -> Self { + pub fn new(bookmarks: Option>) -> Self { + // This may need to be extended in the future + let mut results: Vec = Vec::new(); + results.extend( + vec![ + Message::GoBackward, + Message::GoForward, + Message::Refresh, + Message::GoHome, + Message::CloseCurrentTab, + ] + .into_iter() + .map(|msg| ResultType::Command(msg)), + ); + if let Some(bookmarks) = bookmarks { + results.extend( + bookmarks + .into_iter() + .map(|bookmark| ResultType::Bookmark(bookmark)), + ); + }; + Self { query: String::new(), - commands: Message::iter().map(|e| e.clone().to_string()).collect(), - selected_action: String::new(), - selected_index: 0, + possible_results: results.clone(), + filtered_results: results, + selected_item: None, } } } impl Default for CommandWindowState { fn default() -> Self { - Self::new() + Self::new(None) } } pub fn command_window<'a>( base: impl Into>, - state: &'a CommandWindowState, + state: &CommandWindowState, ) -> Element<'a, Message> { - let window = container(column![ - text_input("Command Menu", &state.query) - .on_input(Message::QueryChanged) - .size(25), - SelectionList::new_with( - &state.commands, - Message::CommandSelectionChanged, - 15., - 5, - |theme: &Theme, _| iced_aw::style::selection_list::Style { - text_color: theme.palette().text, - background: theme.palette().background.into(), - ..Default::default() - }, - None, - Font::DEFAULT - ) - .width(Length::Fill) - .height(Length::Fill) - ]) - .padding(10) - .center(600) - .style(|theme: &Theme| container::Style { - background: Some(theme.palette().background.into()), - border: border::rounded(10), - ..container::Style::default() - }); + let window: iced::widget::Container<'_, super::Message, Theme, iced::Renderer> = + container(column![ + text_input("Command Menu", &state.query) + .on_input(Message::CommandPalatteQueryChanged) + .size(25), + container(results_list( + state.filtered_results.as_slice(), + state.selected_item.as_ref(), + )) + .width(Length::Fill) + .height(Length::Fill) + ]) + .padding(10) + .center(600) + .style(|theme: &Theme| container::Style { + background: Some(theme.palette().background.into()), + border: border::rounded(10), + ..container::Style::default() + }); stack![ base.into(), @@ -81,7 +91,7 @@ pub fn command_window<'a>( ..container::Style::default() } })) - .on_press(Message::HideOverlay) + .on_press(Message::HideOverlay), ) ] .into() diff --git a/src/widgets/command_window/categorical_list.rs b/src/widgets/command_window/categorical_list.rs new file mode 100644 index 0000000..ec0f12d --- /dev/null +++ b/src/widgets/command_window/categorical_list.rs @@ -0,0 +1,323 @@ +//! Build and show dropdown `ListMenus`. + +use super::categorical_selection_list::Catalog; + +use iced::{ + advanced::{ + layout::{Limits, Node}, + renderer, + widget::{ + tree::{State, Tag}, + Tree, + }, + Clipboard, Layout, Shell, Widget, + }, + alignment::{Horizontal, Vertical}, + event, + mouse::{self, Cursor}, + touch, + widget::text::{LineHeight, Wrapping}, + Border, Color, Element, Event, Length, Padding, Pixels, Point, Rectangle, Shadow, Size, +}; +use std::{ + collections::hash_map::DefaultHasher, + fmt::Display, + hash::{Hash, Hasher}, + marker::PhantomData, + rc::Rc, +}; + +/// The Private [`List`] Handles the Actual list rendering. +#[allow(missing_debug_implementations)] +pub struct List<'a, T: 'a, Message, Theme, Renderer> +where + T: Clone + Display + Eq + Hash, + [T]: ToOwned>, + Renderer: renderer::Renderer + iced::advanced::text::Renderer, + Theme: Catalog, +{ + //// Name of the List category + pub category_name: &'a str, + /// Options pointer to hold all items + pub options: &'a [T], + /// Hovered Item Pointer + /// Label Font + pub font: Renderer::Font, + /// Style for Font colors and Box hover colors. + pub class: ::Class<'a>, + /// Function Pointer On Select to call on Mouse button press. + pub on_selected: Rc Message + 'static>, + /// The padding Width + pub padding: Padding, + /// The Text Size + pub text_size: f32, + /// Set the Selected ID manually. + pub selected: Option, + /// Shadow Type holder for Renderer. + pub phantomdata: PhantomData, +} + +/// The Private [`ListState`] Handles the State of the inner list. +#[derive(Debug, Clone, Default)] +pub struct ListState { + /// Statehood of ``hovered_option`` + pub hovered_option: Option, + /// The index in the list of options of the last chosen Item Clicked for Processing + pub last_selected_index: Option<(usize, u64)>, + // String Build Cache + //pub options: Vec, +} + +impl<'a, T, Message, Theme, Renderer> Widget + for List<'a, T, Message, Theme, Renderer> +where + T: Clone + Display + Eq + Hash, + Renderer: renderer::Renderer + iced::advanced::text::Renderer, + Theme: Catalog, +{ + fn tag(&self) -> Tag { + Tag::of::() + } + + fn state(&self) -> State { + State::new(ListState::default()) + } + + fn diff(&self, state: &mut Tree) { + let list_state = state.state.downcast_mut::(); + + if let Some(id) = self.selected { + if let Some(option) = self.options.get(id) { + let mut hasher = DefaultHasher::new(); + option.hash(&mut hasher); + + list_state.last_selected_index = Some((id, hasher.finish())); + } else { + list_state.last_selected_index = None; + } + } else if let Some((id, hash)) = list_state.last_selected_index { + if let Some(option) = self.options.get(id) { + let mut hasher = DefaultHasher::new(); + option.hash(&mut hasher); + + if hash != hasher.finish() { + list_state.last_selected_index = None; + } + } else { + list_state.last_selected_index = None; + } + } + + //list_state.options = self.options.iter().map(ToString::to_string).collect(); + } + + fn size(&self) -> Size { + Size::new(Length::Fill, Length::Shrink) + } + + fn layout(&self, _tree: &mut Tree, _renderer: &Renderer, limits: &Limits) -> Node { + use std::f32; + let limits = limits.height(Length::Fill).width(Length::Fill); + + #[allow(clippy::cast_precision_loss)] + let intrinsic = Size::new( + limits.max().width, + (self.text_size + self.padding.vertical()) * self.options.len() as f32, + ); + + Node::new(intrinsic) + } + + fn on_event( + &mut self, + state: &mut Tree, + event: Event, + layout: Layout<'_>, + cursor: Cursor, + _renderer: &Renderer, + _clipboard: &mut dyn Clipboard, + shell: &mut Shell, + _viewport: &Rectangle, + ) -> event::Status { + let bounds = layout.bounds(); + let mut status = event::Status::Ignored; + let list_state = state.state.downcast_mut::(); + let cursor = cursor.position().unwrap_or_default(); + + if bounds.contains(cursor) { + match event { + Event::Mouse(mouse::Event::CursorMoved { .. }) => { + list_state.hovered_option = Some( + ((cursor.y - bounds.y) / (self.text_size + self.padding.vertical())) + as usize, + ); + } + Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) + | Event::Touch(touch::Event::FingerPressed { .. }) => { + list_state.hovered_option = Some( + ((cursor.y - bounds.y) / (self.text_size + self.padding.vertical())) + as usize, + ); + + if let Some(index) = list_state.hovered_option { + if let Some(option) = self.options.get(index) { + let mut hasher = DefaultHasher::new(); + option.hash(&mut hasher); + list_state.last_selected_index = Some((index, hasher.finish())); + } + } + + status = + list_state + .last_selected_index + .map_or(event::Status::Ignored, |last| { + if let Some(option) = self.options.get(last.0) { + shell.publish((self.on_selected)(last.0, option.clone())); + event::Status::Captured + } else { + event::Status::Ignored + } + }); + } + _ => {} + } + } + + status + } + + fn mouse_interaction( + &self, + _state: &Tree, + layout: Layout<'_>, + cursor: Cursor, + _viewport: &Rectangle, + _renderer: &Renderer, + ) -> mouse::Interaction { + let bounds = layout.bounds(); + + if bounds.contains(cursor.position().unwrap_or_default()) { + mouse::Interaction::Pointer + } else { + mouse::Interaction::default() + } + } + + fn draw( + &self, + state: &Tree, + renderer: &mut Renderer, + theme: &Theme, + _style: &renderer::Style, + layout: Layout<'_>, + _cursor: Cursor, + viewport: &Rectangle, + ) { + use std::f32; + + let bounds = layout.bounds(); + let option_height = self.text_size + self.padding.vertical(); + let offset = viewport.y - bounds.y; + let start = (offset / option_height) as usize; + let end = ((offset + viewport.height) / option_height).ceil() as usize; + let list_state = state.state.downcast_ref::(); + + renderer.fill_text( + iced::advanced::text::Text { + content: self.category_name.to_string(), + bounds: Size::new(f32::INFINITY, bounds.height), + size: Pixels(self.text_size), + font: self.font, + horizontal_alignment: Horizontal::Left, + vertical_alignment: Vertical::Center, + line_height: LineHeight::default(), + shaping: iced::widget::text::Shaping::Advanced, + wrapping: Wrapping::default(), + }, + Point::new(bounds.x, bounds.center_y()), + theme + .style(&self.class, iced_aw::style::Status::Active) + .text_color, + bounds, + ); + + for i in start..end.min(self.options.len()) { + let is_selected = list_state.last_selected_index.is_some_and(|u| u.0 == i); + let is_hovered = list_state.hovered_option == Some(i); + + let bounds = Rectangle { + x: bounds.x, + y: bounds.y + option_height * i as f32, + width: bounds.width, + height: self.text_size + self.padding.vertical(), + }; + + if (is_selected || is_hovered) && (bounds.width > 0.) && (bounds.height > 0.) { + renderer.fill_quad( + renderer::Quad { + bounds, + border: Border { + radius: (0.0).into(), + width: 0.0, + color: Color::TRANSPARENT, + }, + shadow: Shadow::default(), + }, + if is_selected { + theme + .style(&self.class, iced_aw::style::Status::Selected) + .background + } else { + theme + .style(&self.class, iced_aw::style::Status::Hovered) + .background + }, + ); + } + + let text_color = if is_selected { + theme + .style(&self.class, iced_aw::style::Status::Selected) + .text_color + } else if is_hovered { + theme + .style(&self.class, iced_aw::style::Status::Hovered) + .text_color + } else { + theme + .style(&self.class, iced_aw::style::Status::Active) + .text_color + }; + + renderer.fill_text( + iced::advanced::text::Text { + content: self.options[i].to_string(), + bounds: Size::new(f32::INFINITY, bounds.height), + size: Pixels(self.text_size), + font: self.font, + horizontal_alignment: Horizontal::Left, + vertical_alignment: Vertical::Center, + line_height: LineHeight::default(), + shaping: iced::widget::text::Shaping::Advanced, + wrapping: Wrapping::default(), + }, + Point::new(bounds.x, bounds.center_y()), + text_color, + bounds, + ); + } + } +} + +impl<'a, T, Message, Theme, Renderer> From> + for Element<'a, Message, Theme, Renderer> +where + T: Clone + Display + Eq + Hash, + Message: 'a, + Renderer: 'a + renderer::Renderer + iced::advanced::text::Renderer, + Theme: 'a + Catalog, +{ + fn from(list: List<'a, T, Message, Theme, Renderer>) -> Self { + Element::new(list) + } +} diff --git a/src/widgets/command_window/categorical_selection_list.rs b/src/widgets/command_window/categorical_selection_list.rs new file mode 100644 index 0000000..fd6011b --- /dev/null +++ b/src/widgets/command_window/categorical_selection_list.rs @@ -0,0 +1,394 @@ +//! Display a dropdown list of selectable values. +use iced::{ + advanced::{ + layout::{Limits, Node}, + renderer, + text::{paragraph, Paragraph, Text}, + widget::{tree, Tree}, + Clipboard, Layout, Shell, Widget, + }, + alignment::{Horizontal, Vertical}, + event, + mouse::{self, Cursor}, + widget::{ + container, scrollable, + text::{self, LineHeight, Wrapping}, + Column, Container, Scrollable, + }, + Border, Element, Event, Font, Length, Padding, Pixels, Rectangle, Shadow, Size, +}; +pub use iced_aw::style::{ + selection_list::{Catalog, Style}, + Status, StyleFn, +}; +use std::{fmt::Display, hash::Hash, marker::PhantomData, rc::Rc}; + +/// Fork of iced_aw::selection_list::List, but with category name field +use super::categorical_list::List; + +/// A widget for selecting a single value from a dynamic scrollable list of options. +#[allow(missing_debug_implementations)] +#[allow(clippy::type_repetition_in_bounds)] +pub struct SelectionList<'a, T, Message, Theme = iced::Theme, Renderer = iced::Renderer> +where + T: Clone + ToString + Eq + Hash, + [T]: ToOwned>, + Renderer: renderer::Renderer + iced::advanced::text::Renderer, + Theme: Catalog + container::Catalog, +{ + /// Container for Rendering List. + container: Container<'a, Message, Theme, Renderer>, + /// List of Elements to Render. + options: &'a [(&'a str, &'a [T])], + /// Label Font + font: Renderer::Font, + /// The Containers Width + width: Length, + /// The Containers height + height: Length, + /// The padding Width + padding: Padding, + /// The Text Size + text_size: f32, + /// Style for Looks + class: ::Class<'a>, +} + +#[allow(clippy::type_repetition_in_bounds)] +impl<'a, T, Message, Theme, Renderer> SelectionList<'a, T, Message, Theme, Renderer> +where + Message: 'a + Clone, + Renderer: 'a + renderer::Renderer + iced::advanced::text::Renderer, + // Theme: 'a + Catalog + container::Catalog + scrollable::Catalog, + Theme: 'a + + Catalog< + Class<'a> = std::boxed::Box< + dyn std::ops::Fn( + &'a Theme, + iced_aw::style::Status, + ) -> iced_aw::style::selection_list::Style, + >, + > + + container::Catalog + + scrollable::Catalog, + T: Clone + Display + Eq + Hash, + [T]: ToOwned>, +{ + /// Creates a new [`SelectionList`] with the given list of `options`, + /// the current selected value, and the `message` to produce when an option is + /// selected. This will default the `style`, `text_size` and `padding`. use `new_with` + /// to set those. + pub fn new( + options: &'a [(&'a str, &'a [T])], + on_selected: impl Fn(usize, T) -> Message + 'static, + ) -> Self { + let mut lists: Vec> = Vec::new(); + for (category, list) in options { + lists.push( + List { + category_name: &category, + options: list, + font: Font::default(), + text_size: 12.0, + padding: 5.0.into(), + class: ::default(), + on_selected: Box::new(on_selected), + selected: None, + phantomdata: PhantomData, + } + .into(), + ) + } + + let container = Container::new(Scrollable::new(Column::from_vec(lists))).padding(1); + + Self { + options, + font: Font::default(), + class: ::default(), + container, + width: Length::Fill, + height: Length::Fill, + padding: 5.0.into(), + text_size: 12.0, + } + } + + /// Creates a new [`SelectionList`] with the given list of `options`, + /// the current selected value, the message to produce when an option is + /// selected, the `style`, `text_size`, `padding` and `font`. + pub fn new_with( + // category_name: &'a str, + // options: &'a [T], + options: &'a [(&'a str, &'a [T])], + on_selected: impl Fn(usize, T) -> Message + 'static, + text_size: f32, + padding: impl Into, + style: impl Fn(&Theme, Status) -> Style + 'a + Clone, + selected: Option, + font: Font, + ) -> Self + where + ::Class<'a>: From>, + { + let padding = padding.into(); + + let mut lists = Vec::new(); + + let on_selected = Rc::new(Box::new(on_selected)); + for (category_name, options) in options { + lists.push( + List { + category_name, + options, + font, + text_size, + padding, + class: (Box::new(style.clone()) as StyleFn<'a, Theme, Style>).into(), + selected, + on_selected: on_selected.clone(), + phantomdata: PhantomData, + } + .into(), + ) + } + + let container = Container::new(Scrollable::new(Column::from_vec(lists))).padding(1); + + Self { + options, + font, + class: (Box::new(style.clone()) as StyleFn<'a, Theme, Style>).into(), + container, + width: Length::Fill, + height: Length::Fill, + padding, + text_size, + } + } + + /// Sets the width of the [`SelectionList`]. + #[must_use] + pub fn width(mut self, width: impl Into) -> Self { + self.width = width.into(); + self + } + + /// Sets the height of the [`SelectionList`]. + #[must_use] + pub fn height(mut self, height: impl Into) -> Self { + self.height = height.into(); + self + } + + /// Sets the style of the [`SelectionList`]. + #[must_use] + pub fn style(mut self, style: impl Fn(&Theme, Status) -> Style + 'a) -> Self + where + ::Class<'a>: From>, + { + self.class = (Box::new(style) as StyleFn<'a, Theme, Style>).into(); + self + } + + /// Sets the class of the input of the [`SelectionList`]. + #[must_use] + pub fn class(mut self, class: impl Into<::Class<'a>>) -> Self { + self.class = class.into(); + self + } +} + +impl<'a, T, Message, Theme, Renderer> Widget + for SelectionList<'a, T, Message, Theme, Renderer> +where + T: 'a + Clone + ToString + Eq + Hash + Display, + Message: 'static, + Renderer: renderer::Renderer + iced::advanced::text::Renderer + 'a, + Theme: Catalog + container::Catalog, +{ + fn children(&self) -> Vec { + vec![Tree::new(&self.container as &dyn Widget<_, _, _>)] + } + + fn diff(&self, tree: &mut Tree) { + tree.diff_children(&[&self.container as &dyn Widget<_, _, _>]); + let state = tree.state.downcast_mut::>(); + + state.values = self + .options + .iter() + .map(|_| paragraph::Plain::::default()) + .collect(); + } + + fn size(&self) -> Size { + Size::new(self.width, Length::Shrink) + } + + fn tag(&self) -> tree::Tag { + tree::Tag::of::>() + } + + fn state(&self) -> tree::State { + tree::State::new(State::::new(self.options)) + } + + fn layout(&self, tree: &mut Tree, renderer: &Renderer, limits: &Limits) -> Node { + use std::f32; + + let state = tree.state.downcast_mut::>(); + + let limits = limits.width(self.width).height(self.height); + + let max_width = match self.width { + Length::Shrink => self + .options + .iter() + .enumerate() + .map(|(id, val)| { + let s: &str = &val.to_string(); + let text = Text { + content: s, + size: Pixels(self.text_size), + line_height: LineHeight::default(), + bounds: Size::INFINITY, + font: self.font, + horizontal_alignment: Horizontal::Left, + vertical_alignment: Vertical::Top, + shaping: text::Shaping::Advanced, + wrapping: Wrapping::default(), + }; + + state.values[id].update(text); + (state.values[id].min_bounds().width + self.padding.horizontal()).round() as u32 + }) + .max() + .unwrap_or(100), + _ => limits.max().width as u32, + }; + + let limits = limits.max_width(max_width as f32 + self.padding.horizontal()); + + let content = self + .container + .layout(&mut tree.children[0], renderer, &limits); + let size = limits.resolve(self.width, self.height, content.size()); + Node::with_children(size, vec![content]) + } + + fn on_event( + &mut self, + state: &mut Tree, + event: Event, + layout: Layout<'_>, + cursor: Cursor, + renderer: &Renderer, + clipboard: &mut dyn Clipboard, + shell: &mut Shell, + viewport: &Rectangle, + ) -> event::Status { + self.container.on_event( + &mut state.children[0], + event, + layout + .children() + .next() + .expect("Scrollable Child Missing in Selection List"), + cursor, + renderer, + clipboard, + shell, + viewport, + ) + } + + fn mouse_interaction( + &self, + state: &Tree, + layout: Layout<'_>, + cursor: Cursor, + viewport: &Rectangle, + renderer: &Renderer, + ) -> mouse::Interaction { + self.container + .mouse_interaction(&state.children[0], layout, cursor, viewport, renderer) + } + + fn draw( + &self, + state: &Tree, + renderer: &mut Renderer, + theme: &Theme, + style: &renderer::Style, + layout: Layout<'_>, + cursor: Cursor, + viewport: &Rectangle, + ) { + let bounds = layout.bounds(); + let style_sheet = ::style(theme, &self.class, Status::Active); + + if let Some(clipped_viewport) = bounds.intersection(viewport) { + renderer.fill_quad( + renderer::Quad { + bounds, + border: Border { + radius: (0.0).into(), + width: style_sheet.border_width, + color: style_sheet.border_color, + }, + shadow: Shadow::default(), + }, + style_sheet.background, + ); + + self.container.draw( + &state.children[0], + renderer, + theme, + style, + layout + .children() + .next() + .expect("Scrollable Child Missing in Selection List"), + cursor, + &clipped_viewport, + ); + } + } +} + +impl<'a, T, Message, Theme, Renderer> From> + for Element<'a, Message, Theme, Renderer> +where + T: Clone + ToString + Eq + Hash + Display, + Message: 'static, + Renderer: 'a + renderer::Renderer + iced::advanced::text::Renderer, + Theme: 'a + Catalog + container::Catalog, +{ + fn from(selection_list: SelectionList<'a, T, Message, Theme, Renderer>) -> Self { + Element::new(selection_list) + } +} + +/// A Paragraph cache to enhance speed of layouting. +#[derive(Default, Clone)] +pub struct State { + values: Vec>, +} + +impl State

{ + /// Creates a new [`State`], representing an unfocused [`TextInput`](iced::widget::TextInput). + pub fn new(options: &[T]) -> Self + where + T: Clone + Display + Eq + Hash, + [T]: ToOwned>, + { + Self { + values: options + .iter() + .map(|_| paragraph::Plain::

::default()) + .collect(), + } + } +} diff --git a/src/widgets/command_window/results.rs b/src/widgets/command_window/results.rs new file mode 100644 index 0000000..e58a8a2 --- /dev/null +++ b/src/widgets/command_window/results.rs @@ -0,0 +1,52 @@ +use iced::widget::{scrollable, text, Column}; +use iced::{Element, Length, Renderer, Theme}; +use strum_macros::Display; + +use super::super::{Bookmark, Message}; + +#[derive(Clone, Display, PartialEq)] +pub enum ResultType { + Command(Message), + Bookmark(Bookmark), +} + +impl ResultType { + pub fn as_string(&self) -> String { + match self { + ResultType::Command(command) => command.to_string(), + ResultType::Bookmark(bookmark) => format!("Go to: {}", bookmark.url()), + } + } +} + +pub fn results_list<'a>( + results: &[ResultType], + selected_item: Option<&String>, +) -> Element<'a, Message, Theme, Renderer> { + let mut list = Vec::new(); + let mut result_types = Vec::new(); + + for result in results { + if !result_types.contains(&result.to_string()) { + result_types.push(result.to_string()); + list.push(text(result.to_string()).size(20).into()) + } + let result_text = match result { + ResultType::Command(command) => command.to_string(), + ResultType::Bookmark(bookmark) => bookmark.to_string(), + }; + + let mut text = text(format!(" {}", result_text)).size(15); + if let Some(selected_item) = selected_item { + if result_text == *selected_item { + // text. + } + } + list.push(text.into()) + } + + scrollable(Column::from_vec(list)) + .width(Length::Fill) + .spacing(10) + .into() +} diff --git a/src/widgets/mod.rs b/src/widgets/mod.rs index b524bdb..ac2ad93 100644 --- a/src/widgets/mod.rs +++ b/src/widgets/mod.rs @@ -22,7 +22,7 @@ pub mod bookmark_bar; pub use bookmark_bar::bookmark_bar; pub mod command_window; -pub use command_window::command_window; +pub use command_window::{command_window, results_list, ResultType}; use crate::{ engines::BrowserEngine, shortcut::check_shortcut, to_url, Bookmark, Bookmarks, ImageInfo, @@ -45,7 +45,7 @@ pub trait CustomWidget { // Options exist only to have defaults for EnumIter #[derive(Debug, Clone, PartialEq, Display, EnumIter)] pub enum Message { - // Commands + // Commands visible to user with shortcuts and command palatte #[strum(to_string = "Go Backward")] GoBackward, #[strum(to_string = "Go Forward")] @@ -74,9 +74,8 @@ pub enum Message { Update, UrlChanged(String), UpdateUrl, - QueryChanged(String), - CommandSelectionChanged(usize, String), - CommandSelectionSelected, + CommandPalatteQueryChanged(String), + CommandPalatteSelected(String), SendKeyboardEvent(Option), SendMouseEvent(Point, Option), UpdateViewSize(Size), @@ -115,7 +114,7 @@ impl Default for IcyBrowser { engine: Engine::new(), home, nav_bar_state: None, - command_window_state: CommandWindowState::new(), + command_window_state: CommandWindowState::new(None), with_tab_bar: false, with_nav_bar: false, bookmarks: None, @@ -151,6 +150,7 @@ impl IcyBrowser { pub fn with_bookmark_bar(mut self, bookmarks: &[Bookmark]) -> Self { self.bookmarks = Some(bookmarks.to_vec()); + self.command_window_state = CommandWindowState::new(self.bookmarks.clone()); self } @@ -316,18 +316,15 @@ impl IcyBrowser { } Task::none() } - Message::QueryChanged(query) => { + Message::CommandPalatteQueryChanged(query) => { self.command_window_state.query = query; + // self.command_window_state.filtered_results = self.command_window_state.possible_results.iter() Task::none() } - Message::CommandSelectionChanged(index, name) => { - self.command_window_state.selected_index = index; - self.command_window_state.selected_action = name; + Message::CommandPalatteSelected(item) => { + self.command_window_state.selected_item = Some(item); Task::none() } - Message::CommandSelectionSelected => { - unimplemented!() - } Message::ToggleOverlay => { if self.show_overlay { Task::done(Message::HideOverlay) From 02b9d440c3972c148218e2bc624067d7b31fa828 Mon Sep 17 00:00:00 2001 From: sawyer bristol Date: Tue, 8 Oct 2024 17:13:06 -0600 Subject: [PATCH 2/9] remove iced_aw implementation --- .../command_window/categorical_list.rs | 323 -------------- .../categorical_selection_list.rs | 394 ------------------ 2 files changed, 717 deletions(-) delete mode 100644 src/widgets/command_window/categorical_list.rs delete mode 100644 src/widgets/command_window/categorical_selection_list.rs diff --git a/src/widgets/command_window/categorical_list.rs b/src/widgets/command_window/categorical_list.rs deleted file mode 100644 index ec0f12d..0000000 --- a/src/widgets/command_window/categorical_list.rs +++ /dev/null @@ -1,323 +0,0 @@ -//! Build and show dropdown `ListMenus`. - -use super::categorical_selection_list::Catalog; - -use iced::{ - advanced::{ - layout::{Limits, Node}, - renderer, - widget::{ - tree::{State, Tag}, - Tree, - }, - Clipboard, Layout, Shell, Widget, - }, - alignment::{Horizontal, Vertical}, - event, - mouse::{self, Cursor}, - touch, - widget::text::{LineHeight, Wrapping}, - Border, Color, Element, Event, Length, Padding, Pixels, Point, Rectangle, Shadow, Size, -}; -use std::{ - collections::hash_map::DefaultHasher, - fmt::Display, - hash::{Hash, Hasher}, - marker::PhantomData, - rc::Rc, -}; - -/// The Private [`List`] Handles the Actual list rendering. -#[allow(missing_debug_implementations)] -pub struct List<'a, T: 'a, Message, Theme, Renderer> -where - T: Clone + Display + Eq + Hash, - [T]: ToOwned>, - Renderer: renderer::Renderer + iced::advanced::text::Renderer, - Theme: Catalog, -{ - //// Name of the List category - pub category_name: &'a str, - /// Options pointer to hold all items - pub options: &'a [T], - /// Hovered Item Pointer - /// Label Font - pub font: Renderer::Font, - /// Style for Font colors and Box hover colors. - pub class: ::Class<'a>, - /// Function Pointer On Select to call on Mouse button press. - pub on_selected: Rc Message + 'static>, - /// The padding Width - pub padding: Padding, - /// The Text Size - pub text_size: f32, - /// Set the Selected ID manually. - pub selected: Option, - /// Shadow Type holder for Renderer. - pub phantomdata: PhantomData, -} - -/// The Private [`ListState`] Handles the State of the inner list. -#[derive(Debug, Clone, Default)] -pub struct ListState { - /// Statehood of ``hovered_option`` - pub hovered_option: Option, - /// The index in the list of options of the last chosen Item Clicked for Processing - pub last_selected_index: Option<(usize, u64)>, - // String Build Cache - //pub options: Vec, -} - -impl<'a, T, Message, Theme, Renderer> Widget - for List<'a, T, Message, Theme, Renderer> -where - T: Clone + Display + Eq + Hash, - Renderer: renderer::Renderer + iced::advanced::text::Renderer, - Theme: Catalog, -{ - fn tag(&self) -> Tag { - Tag::of::() - } - - fn state(&self) -> State { - State::new(ListState::default()) - } - - fn diff(&self, state: &mut Tree) { - let list_state = state.state.downcast_mut::(); - - if let Some(id) = self.selected { - if let Some(option) = self.options.get(id) { - let mut hasher = DefaultHasher::new(); - option.hash(&mut hasher); - - list_state.last_selected_index = Some((id, hasher.finish())); - } else { - list_state.last_selected_index = None; - } - } else if let Some((id, hash)) = list_state.last_selected_index { - if let Some(option) = self.options.get(id) { - let mut hasher = DefaultHasher::new(); - option.hash(&mut hasher); - - if hash != hasher.finish() { - list_state.last_selected_index = None; - } - } else { - list_state.last_selected_index = None; - } - } - - //list_state.options = self.options.iter().map(ToString::to_string).collect(); - } - - fn size(&self) -> Size { - Size::new(Length::Fill, Length::Shrink) - } - - fn layout(&self, _tree: &mut Tree, _renderer: &Renderer, limits: &Limits) -> Node { - use std::f32; - let limits = limits.height(Length::Fill).width(Length::Fill); - - #[allow(clippy::cast_precision_loss)] - let intrinsic = Size::new( - limits.max().width, - (self.text_size + self.padding.vertical()) * self.options.len() as f32, - ); - - Node::new(intrinsic) - } - - fn on_event( - &mut self, - state: &mut Tree, - event: Event, - layout: Layout<'_>, - cursor: Cursor, - _renderer: &Renderer, - _clipboard: &mut dyn Clipboard, - shell: &mut Shell, - _viewport: &Rectangle, - ) -> event::Status { - let bounds = layout.bounds(); - let mut status = event::Status::Ignored; - let list_state = state.state.downcast_mut::(); - let cursor = cursor.position().unwrap_or_default(); - - if bounds.contains(cursor) { - match event { - Event::Mouse(mouse::Event::CursorMoved { .. }) => { - list_state.hovered_option = Some( - ((cursor.y - bounds.y) / (self.text_size + self.padding.vertical())) - as usize, - ); - } - Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) - | Event::Touch(touch::Event::FingerPressed { .. }) => { - list_state.hovered_option = Some( - ((cursor.y - bounds.y) / (self.text_size + self.padding.vertical())) - as usize, - ); - - if let Some(index) = list_state.hovered_option { - if let Some(option) = self.options.get(index) { - let mut hasher = DefaultHasher::new(); - option.hash(&mut hasher); - list_state.last_selected_index = Some((index, hasher.finish())); - } - } - - status = - list_state - .last_selected_index - .map_or(event::Status::Ignored, |last| { - if let Some(option) = self.options.get(last.0) { - shell.publish((self.on_selected)(last.0, option.clone())); - event::Status::Captured - } else { - event::Status::Ignored - } - }); - } - _ => {} - } - } - - status - } - - fn mouse_interaction( - &self, - _state: &Tree, - layout: Layout<'_>, - cursor: Cursor, - _viewport: &Rectangle, - _renderer: &Renderer, - ) -> mouse::Interaction { - let bounds = layout.bounds(); - - if bounds.contains(cursor.position().unwrap_or_default()) { - mouse::Interaction::Pointer - } else { - mouse::Interaction::default() - } - } - - fn draw( - &self, - state: &Tree, - renderer: &mut Renderer, - theme: &Theme, - _style: &renderer::Style, - layout: Layout<'_>, - _cursor: Cursor, - viewport: &Rectangle, - ) { - use std::f32; - - let bounds = layout.bounds(); - let option_height = self.text_size + self.padding.vertical(); - let offset = viewport.y - bounds.y; - let start = (offset / option_height) as usize; - let end = ((offset + viewport.height) / option_height).ceil() as usize; - let list_state = state.state.downcast_ref::(); - - renderer.fill_text( - iced::advanced::text::Text { - content: self.category_name.to_string(), - bounds: Size::new(f32::INFINITY, bounds.height), - size: Pixels(self.text_size), - font: self.font, - horizontal_alignment: Horizontal::Left, - vertical_alignment: Vertical::Center, - line_height: LineHeight::default(), - shaping: iced::widget::text::Shaping::Advanced, - wrapping: Wrapping::default(), - }, - Point::new(bounds.x, bounds.center_y()), - theme - .style(&self.class, iced_aw::style::Status::Active) - .text_color, - bounds, - ); - - for i in start..end.min(self.options.len()) { - let is_selected = list_state.last_selected_index.is_some_and(|u| u.0 == i); - let is_hovered = list_state.hovered_option == Some(i); - - let bounds = Rectangle { - x: bounds.x, - y: bounds.y + option_height * i as f32, - width: bounds.width, - height: self.text_size + self.padding.vertical(), - }; - - if (is_selected || is_hovered) && (bounds.width > 0.) && (bounds.height > 0.) { - renderer.fill_quad( - renderer::Quad { - bounds, - border: Border { - radius: (0.0).into(), - width: 0.0, - color: Color::TRANSPARENT, - }, - shadow: Shadow::default(), - }, - if is_selected { - theme - .style(&self.class, iced_aw::style::Status::Selected) - .background - } else { - theme - .style(&self.class, iced_aw::style::Status::Hovered) - .background - }, - ); - } - - let text_color = if is_selected { - theme - .style(&self.class, iced_aw::style::Status::Selected) - .text_color - } else if is_hovered { - theme - .style(&self.class, iced_aw::style::Status::Hovered) - .text_color - } else { - theme - .style(&self.class, iced_aw::style::Status::Active) - .text_color - }; - - renderer.fill_text( - iced::advanced::text::Text { - content: self.options[i].to_string(), - bounds: Size::new(f32::INFINITY, bounds.height), - size: Pixels(self.text_size), - font: self.font, - horizontal_alignment: Horizontal::Left, - vertical_alignment: Vertical::Center, - line_height: LineHeight::default(), - shaping: iced::widget::text::Shaping::Advanced, - wrapping: Wrapping::default(), - }, - Point::new(bounds.x, bounds.center_y()), - text_color, - bounds, - ); - } - } -} - -impl<'a, T, Message, Theme, Renderer> From> - for Element<'a, Message, Theme, Renderer> -where - T: Clone + Display + Eq + Hash, - Message: 'a, - Renderer: 'a + renderer::Renderer + iced::advanced::text::Renderer, - Theme: 'a + Catalog, -{ - fn from(list: List<'a, T, Message, Theme, Renderer>) -> Self { - Element::new(list) - } -} diff --git a/src/widgets/command_window/categorical_selection_list.rs b/src/widgets/command_window/categorical_selection_list.rs deleted file mode 100644 index fd6011b..0000000 --- a/src/widgets/command_window/categorical_selection_list.rs +++ /dev/null @@ -1,394 +0,0 @@ -//! Display a dropdown list of selectable values. -use iced::{ - advanced::{ - layout::{Limits, Node}, - renderer, - text::{paragraph, Paragraph, Text}, - widget::{tree, Tree}, - Clipboard, Layout, Shell, Widget, - }, - alignment::{Horizontal, Vertical}, - event, - mouse::{self, Cursor}, - widget::{ - container, scrollable, - text::{self, LineHeight, Wrapping}, - Column, Container, Scrollable, - }, - Border, Element, Event, Font, Length, Padding, Pixels, Rectangle, Shadow, Size, -}; -pub use iced_aw::style::{ - selection_list::{Catalog, Style}, - Status, StyleFn, -}; -use std::{fmt::Display, hash::Hash, marker::PhantomData, rc::Rc}; - -/// Fork of iced_aw::selection_list::List, but with category name field -use super::categorical_list::List; - -/// A widget for selecting a single value from a dynamic scrollable list of options. -#[allow(missing_debug_implementations)] -#[allow(clippy::type_repetition_in_bounds)] -pub struct SelectionList<'a, T, Message, Theme = iced::Theme, Renderer = iced::Renderer> -where - T: Clone + ToString + Eq + Hash, - [T]: ToOwned>, - Renderer: renderer::Renderer + iced::advanced::text::Renderer, - Theme: Catalog + container::Catalog, -{ - /// Container for Rendering List. - container: Container<'a, Message, Theme, Renderer>, - /// List of Elements to Render. - options: &'a [(&'a str, &'a [T])], - /// Label Font - font: Renderer::Font, - /// The Containers Width - width: Length, - /// The Containers height - height: Length, - /// The padding Width - padding: Padding, - /// The Text Size - text_size: f32, - /// Style for Looks - class: ::Class<'a>, -} - -#[allow(clippy::type_repetition_in_bounds)] -impl<'a, T, Message, Theme, Renderer> SelectionList<'a, T, Message, Theme, Renderer> -where - Message: 'a + Clone, - Renderer: 'a + renderer::Renderer + iced::advanced::text::Renderer, - // Theme: 'a + Catalog + container::Catalog + scrollable::Catalog, - Theme: 'a - + Catalog< - Class<'a> = std::boxed::Box< - dyn std::ops::Fn( - &'a Theme, - iced_aw::style::Status, - ) -> iced_aw::style::selection_list::Style, - >, - > - + container::Catalog - + scrollable::Catalog, - T: Clone + Display + Eq + Hash, - [T]: ToOwned>, -{ - /// Creates a new [`SelectionList`] with the given list of `options`, - /// the current selected value, and the `message` to produce when an option is - /// selected. This will default the `style`, `text_size` and `padding`. use `new_with` - /// to set those. - pub fn new( - options: &'a [(&'a str, &'a [T])], - on_selected: impl Fn(usize, T) -> Message + 'static, - ) -> Self { - let mut lists: Vec> = Vec::new(); - for (category, list) in options { - lists.push( - List { - category_name: &category, - options: list, - font: Font::default(), - text_size: 12.0, - padding: 5.0.into(), - class: ::default(), - on_selected: Box::new(on_selected), - selected: None, - phantomdata: PhantomData, - } - .into(), - ) - } - - let container = Container::new(Scrollable::new(Column::from_vec(lists))).padding(1); - - Self { - options, - font: Font::default(), - class: ::default(), - container, - width: Length::Fill, - height: Length::Fill, - padding: 5.0.into(), - text_size: 12.0, - } - } - - /// Creates a new [`SelectionList`] with the given list of `options`, - /// the current selected value, the message to produce when an option is - /// selected, the `style`, `text_size`, `padding` and `font`. - pub fn new_with( - // category_name: &'a str, - // options: &'a [T], - options: &'a [(&'a str, &'a [T])], - on_selected: impl Fn(usize, T) -> Message + 'static, - text_size: f32, - padding: impl Into, - style: impl Fn(&Theme, Status) -> Style + 'a + Clone, - selected: Option, - font: Font, - ) -> Self - where - ::Class<'a>: From>, - { - let padding = padding.into(); - - let mut lists = Vec::new(); - - let on_selected = Rc::new(Box::new(on_selected)); - for (category_name, options) in options { - lists.push( - List { - category_name, - options, - font, - text_size, - padding, - class: (Box::new(style.clone()) as StyleFn<'a, Theme, Style>).into(), - selected, - on_selected: on_selected.clone(), - phantomdata: PhantomData, - } - .into(), - ) - } - - let container = Container::new(Scrollable::new(Column::from_vec(lists))).padding(1); - - Self { - options, - font, - class: (Box::new(style.clone()) as StyleFn<'a, Theme, Style>).into(), - container, - width: Length::Fill, - height: Length::Fill, - padding, - text_size, - } - } - - /// Sets the width of the [`SelectionList`]. - #[must_use] - pub fn width(mut self, width: impl Into) -> Self { - self.width = width.into(); - self - } - - /// Sets the height of the [`SelectionList`]. - #[must_use] - pub fn height(mut self, height: impl Into) -> Self { - self.height = height.into(); - self - } - - /// Sets the style of the [`SelectionList`]. - #[must_use] - pub fn style(mut self, style: impl Fn(&Theme, Status) -> Style + 'a) -> Self - where - ::Class<'a>: From>, - { - self.class = (Box::new(style) as StyleFn<'a, Theme, Style>).into(); - self - } - - /// Sets the class of the input of the [`SelectionList`]. - #[must_use] - pub fn class(mut self, class: impl Into<::Class<'a>>) -> Self { - self.class = class.into(); - self - } -} - -impl<'a, T, Message, Theme, Renderer> Widget - for SelectionList<'a, T, Message, Theme, Renderer> -where - T: 'a + Clone + ToString + Eq + Hash + Display, - Message: 'static, - Renderer: renderer::Renderer + iced::advanced::text::Renderer + 'a, - Theme: Catalog + container::Catalog, -{ - fn children(&self) -> Vec { - vec![Tree::new(&self.container as &dyn Widget<_, _, _>)] - } - - fn diff(&self, tree: &mut Tree) { - tree.diff_children(&[&self.container as &dyn Widget<_, _, _>]); - let state = tree.state.downcast_mut::>(); - - state.values = self - .options - .iter() - .map(|_| paragraph::Plain::::default()) - .collect(); - } - - fn size(&self) -> Size { - Size::new(self.width, Length::Shrink) - } - - fn tag(&self) -> tree::Tag { - tree::Tag::of::>() - } - - fn state(&self) -> tree::State { - tree::State::new(State::::new(self.options)) - } - - fn layout(&self, tree: &mut Tree, renderer: &Renderer, limits: &Limits) -> Node { - use std::f32; - - let state = tree.state.downcast_mut::>(); - - let limits = limits.width(self.width).height(self.height); - - let max_width = match self.width { - Length::Shrink => self - .options - .iter() - .enumerate() - .map(|(id, val)| { - let s: &str = &val.to_string(); - let text = Text { - content: s, - size: Pixels(self.text_size), - line_height: LineHeight::default(), - bounds: Size::INFINITY, - font: self.font, - horizontal_alignment: Horizontal::Left, - vertical_alignment: Vertical::Top, - shaping: text::Shaping::Advanced, - wrapping: Wrapping::default(), - }; - - state.values[id].update(text); - (state.values[id].min_bounds().width + self.padding.horizontal()).round() as u32 - }) - .max() - .unwrap_or(100), - _ => limits.max().width as u32, - }; - - let limits = limits.max_width(max_width as f32 + self.padding.horizontal()); - - let content = self - .container - .layout(&mut tree.children[0], renderer, &limits); - let size = limits.resolve(self.width, self.height, content.size()); - Node::with_children(size, vec![content]) - } - - fn on_event( - &mut self, - state: &mut Tree, - event: Event, - layout: Layout<'_>, - cursor: Cursor, - renderer: &Renderer, - clipboard: &mut dyn Clipboard, - shell: &mut Shell, - viewport: &Rectangle, - ) -> event::Status { - self.container.on_event( - &mut state.children[0], - event, - layout - .children() - .next() - .expect("Scrollable Child Missing in Selection List"), - cursor, - renderer, - clipboard, - shell, - viewport, - ) - } - - fn mouse_interaction( - &self, - state: &Tree, - layout: Layout<'_>, - cursor: Cursor, - viewport: &Rectangle, - renderer: &Renderer, - ) -> mouse::Interaction { - self.container - .mouse_interaction(&state.children[0], layout, cursor, viewport, renderer) - } - - fn draw( - &self, - state: &Tree, - renderer: &mut Renderer, - theme: &Theme, - style: &renderer::Style, - layout: Layout<'_>, - cursor: Cursor, - viewport: &Rectangle, - ) { - let bounds = layout.bounds(); - let style_sheet = ::style(theme, &self.class, Status::Active); - - if let Some(clipped_viewport) = bounds.intersection(viewport) { - renderer.fill_quad( - renderer::Quad { - bounds, - border: Border { - radius: (0.0).into(), - width: style_sheet.border_width, - color: style_sheet.border_color, - }, - shadow: Shadow::default(), - }, - style_sheet.background, - ); - - self.container.draw( - &state.children[0], - renderer, - theme, - style, - layout - .children() - .next() - .expect("Scrollable Child Missing in Selection List"), - cursor, - &clipped_viewport, - ); - } - } -} - -impl<'a, T, Message, Theme, Renderer> From> - for Element<'a, Message, Theme, Renderer> -where - T: Clone + ToString + Eq + Hash + Display, - Message: 'static, - Renderer: 'a + renderer::Renderer + iced::advanced::text::Renderer, - Theme: 'a + Catalog + container::Catalog, -{ - fn from(selection_list: SelectionList<'a, T, Message, Theme, Renderer>) -> Self { - Element::new(selection_list) - } -} - -/// A Paragraph cache to enhance speed of layouting. -#[derive(Default, Clone)] -pub struct State { - values: Vec>, -} - -impl State

{ - /// Creates a new [`State`], representing an unfocused [`TextInput`](iced::widget::TextInput). - pub fn new(options: &[T]) -> Self - where - T: Clone + Display + Eq + Hash, - [T]: ToOwned>, - { - Self { - values: options - .iter() - .map(|_| paragraph::Plain::

::default()) - .collect(), - } - } -} From 29d6fd086a132d90d025277620a412874bc8da5e Mon Sep 17 00:00:00 2001 From: sawyer bristol Date: Tue, 8 Oct 2024 19:07:46 -0600 Subject: [PATCH 3/9] can filter down results - filtering by sub type not working --- src/widgets/command_window/results.rs | 14 +++++++------- src/widgets/mod.rs | 22 +++++++++++++++++----- 2 files changed, 24 insertions(+), 12 deletions(-) diff --git a/src/widgets/command_window/results.rs b/src/widgets/command_window/results.rs index e58a8a2..88b18dc 100644 --- a/src/widgets/command_window/results.rs +++ b/src/widgets/command_window/results.rs @@ -6,15 +6,15 @@ use super::super::{Bookmark, Message}; #[derive(Clone, Display, PartialEq)] pub enum ResultType { - Command(Message), - Bookmark(Bookmark), + Commands(Message), + Bookmarks(Bookmark), } impl ResultType { - pub fn as_string(&self) -> String { + pub fn inner_name(&self) -> String { match self { - ResultType::Command(command) => command.to_string(), - ResultType::Bookmark(bookmark) => format!("Go to: {}", bookmark.url()), + ResultType::Commands(command) => command.to_string(), + ResultType::Bookmarks(bookmark) => format!("Go to: {}", bookmark.url()), } } } @@ -32,8 +32,8 @@ pub fn results_list<'a>( list.push(text(result.to_string()).size(20).into()) } let result_text = match result { - ResultType::Command(command) => command.to_string(), - ResultType::Bookmark(bookmark) => bookmark.to_string(), + ResultType::Commands(command) => command.to_string(), + ResultType::Bookmarks(bookmark) => bookmark.to_string(), }; let mut text = text(format!(" {}", result_text)).size(15); diff --git a/src/widgets/mod.rs b/src/widgets/mod.rs index ac2ad93..c44704c 100644 --- a/src/widgets/mod.rs +++ b/src/widgets/mod.rs @@ -46,12 +46,12 @@ pub trait CustomWidget { #[derive(Debug, Clone, PartialEq, Display, EnumIter)] pub enum Message { // Commands visible to user with shortcuts and command palatte - #[strum(to_string = "Go Backward")] + #[strum(to_string = "Go Backward (Back)")] GoBackward, - #[strum(to_string = "Go Forward")] + #[strum(to_string = "Go Forward (Forward)")] GoForward, Refresh, - #[strum(to_string = "Go Home")] + #[strum(to_string = "Go Home (Home)")] GoHome, #[strum(to_string = "Go To Url")] GoToUrl(String), @@ -317,8 +317,20 @@ impl IcyBrowser { Task::none() } Message::CommandPalatteQueryChanged(query) => { - self.command_window_state.query = query; - // self.command_window_state.filtered_results = self.command_window_state.possible_results.iter() + self.command_window_state.query = query.clone(); + self.command_window_state.filtered_results = self + .command_window_state + .possible_results + .clone() + .into_iter() + .filter(|command| { + command + .to_string() + .to_lowercase() + .contains(&query.to_lowercase()) + || command.inner_name().contains(&query.to_lowercase()) + }) + .collect(); Task::none() } Message::CommandPalatteSelected(item) => { From cbde20be99f0639287f3cf5998549d4ec59bd9fc Mon Sep 17 00:00:00 2001 From: sawyer bristol Date: Tue, 8 Oct 2024 21:40:39 -0400 Subject: [PATCH 4/9] fix command being renamed --- src/widgets/command_window.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/widgets/command_window.rs b/src/widgets/command_window.rs index 8dc7e03..98dc893 100644 --- a/src/widgets/command_window.rs +++ b/src/widgets/command_window.rs @@ -27,13 +27,13 @@ impl CommandWindowState { Message::CloseCurrentTab, ] .into_iter() - .map(|msg| ResultType::Command(msg)), + .map(|msg| ResultType::Commands(msg)), ); if let Some(bookmarks) = bookmarks { results.extend( bookmarks .into_iter() - .map(|bookmark| ResultType::Bookmark(bookmark)), + .map(|bookmark| ResultType::Bookmarks(bookmark)), ); }; From 54de3c7a0233abf47a892d3a6e05d45009de50e1 Mon Sep 17 00:00:00 2001 From: sawyer bristol Date: Tue, 8 Oct 2024 22:02:35 -0400 Subject: [PATCH 5/9] fix filtering the query as lowercase --- src/widgets/mod.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/widgets/mod.rs b/src/widgets/mod.rs index c44704c..c6ddc99 100644 --- a/src/widgets/mod.rs +++ b/src/widgets/mod.rs @@ -328,7 +328,10 @@ impl IcyBrowser { .to_string() .to_lowercase() .contains(&query.to_lowercase()) - || command.inner_name().contains(&query.to_lowercase()) + || command + .inner_name() + .to_lowercase() + .contains(&query.to_lowercase()) }) .collect(); Task::none() From a7aa34df71f7eda97b8b641b7b963dd0e4a6d268 Mon Sep 17 00:00:00 2001 From: sawyer bristol Date: Wed, 9 Oct 2024 01:52:37 -0400 Subject: [PATCH 6/9] fixes --- src/widgets/command_window.rs | 2 +- src/widgets/command_window/results.rs | 113 ++++++++++++++++++++------ src/widgets/mod.rs | 2 +- 3 files changed, 91 insertions(+), 26 deletions(-) diff --git a/src/widgets/command_window.rs b/src/widgets/command_window.rs index 98dc893..cfa2bd3 100644 --- a/src/widgets/command_window.rs +++ b/src/widgets/command_window.rs @@ -5,7 +5,7 @@ use super::Message; use crate::Bookmark; mod results; -pub use results::{results_list, ResultType}; +pub use results::{results_list, ResultType, ResultsList}; pub struct CommandWindowState { pub query: String, diff --git a/src/widgets/command_window/results.rs b/src/widgets/command_window/results.rs index 88b18dc..a2eb80e 100644 --- a/src/widgets/command_window/results.rs +++ b/src/widgets/command_window/results.rs @@ -1,5 +1,8 @@ +use iced::advanced::layout::{self, Layout}; +use iced::advanced::renderer; +use iced::advanced::widget::{self, Widget}; use iced::widget::{scrollable, text, Column}; -use iced::{Element, Length, Renderer, Theme}; +use iced::{Element, Length, Size}; use strum_macros::Display; use super::super::{Bookmark, Message}; @@ -20,33 +23,95 @@ impl ResultType { } pub fn results_list<'a>( - results: &[ResultType], - selected_item: Option<&String>, -) -> Element<'a, Message, Theme, Renderer> { - let mut list = Vec::new(); - let mut result_types = Vec::new(); - - for result in results { - if !result_types.contains(&result.to_string()) { - result_types.push(result.to_string()); - list.push(text(result.to_string()).size(20).into()) + results: &'a [ResultType], + selected_item: Option<&'a String>, +) -> ResultsList<'a> { + ResultsList { + results, + selected_item, + } +} + +pub struct ResultsList<'a> { + results: &'a [ResultType], + selected_item: Option<&'a String>, +} + +impl<'a> ResultsList<'a> { + pub fn new(results: &'a [ResultType], selected_item: Option<&'a String>) -> Self { + ResultsList { + results, + selected_item, } - let result_text = match result { - ResultType::Commands(command) => command.to_string(), - ResultType::Bookmarks(bookmark) => bookmark.to_string(), - }; + } +} - let mut text = text(format!(" {}", result_text)).size(15); - if let Some(selected_item) = selected_item { - if result_text == *selected_item { - // text. +impl Widget for ResultsList<'_> +where + Renderer: renderer::Renderer + iced::advanced::text::Renderer, + Theme: iced::widget::scrollable::Catalog + iced::widget::text::Catalog, +{ + fn size(&self) -> Size { + Size { + width: Length::Fill, + height: Length::Fill, + } + } + + fn layout( + &self, + _tree: &mut widget::Tree, + _renderer: &Renderer, + limits: &layout::Limits, + ) -> layout::Node { + layout::Node::new(limits.max()) + } + + fn draw( + &self, + tree: &widget::Tree, + renderer: &mut Renderer, + theme: &Theme, + style: &renderer::Style, + layout: Layout<'_>, + cursor: iced::advanced::mouse::Cursor, + viewport: &iced::Rectangle, + ) { + let mut list: Vec> = Vec::new(); + let mut result_types = Vec::new(); + + for result in self.results { + if !result_types.contains(&result.to_string()) { + result_types.push(result.to_string()); + list.push(text(result.to_string()).size(20).into()) } + let result_text = match result { + ResultType::Commands(command) => command.to_string(), + ResultType::Bookmarks(bookmark) => bookmark.to_string(), + }; + + let mut text = text(format!(" {}", result_text)).size(15); + if let Some(selected_item) = self.selected_item { + if result_text == *selected_item { + // text. + } + } + list.push(text.into()) } - list.push(text.into()) + + scrollable(Column::from_vec(list)) + .width(Length::Fill) + .spacing(10) + .draw(tree, renderer, theme, style, layout, cursor, viewport) } +} - scrollable(Column::from_vec(list)) - .width(Length::Fill) - .spacing(10) - .into() +impl<'a, Message, Theme, Renderer> From> for Element<'a, Message, Theme, Renderer> +where + Renderer: renderer::Renderer + iced::advanced::text::Renderer, + Theme: iced::widget::scrollable::Catalog + iced::widget::text::Catalog, +{ + fn from(results_list: ResultsList) -> Self { + Self::new(results_list) + } } diff --git a/src/widgets/mod.rs b/src/widgets/mod.rs index c6ddc99..f1d1613 100644 --- a/src/widgets/mod.rs +++ b/src/widgets/mod.rs @@ -22,7 +22,7 @@ pub mod bookmark_bar; pub use bookmark_bar::bookmark_bar; pub mod command_window; -pub use command_window::{command_window, results_list, ResultType}; +pub use command_window::{command_window, ResultType, ResultsList}; use crate::{ engines::BrowserEngine, shortcut::check_shortcut, to_url, Bookmark, Bookmarks, ImageInfo, From 64f64c91a579e917220d67ee4e3c26f146a4b6da Mon Sep 17 00:00:00 2001 From: sawyer bristol Date: Thu, 10 Oct 2024 16:45:19 -0600 Subject: [PATCH 7/9] can now use arrow keys on command palatte --- Cargo.lock | 340 +++++++++++++------------- Cargo.toml | 1 + src/lib.rs | 2 +- src/widgets/command_window.rs | 149 +++++++++-- src/widgets/command_window/results.rs | 117 --------- src/widgets/mod.rs | 35 ++- 6 files changed, 319 insertions(+), 325 deletions(-) delete mode 100644 src/widgets/command_window/results.rs diff --git a/Cargo.lock b/Cargo.lock index 5336d3b..d9fefe4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,9 +4,9 @@ version = 3 [[package]] name = "ab_glyph" -version = "0.2.28" +version = "0.2.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79faae4620f45232f599d9bc7b290f88247a0834162c4495ab2f02d60004adfb" +checksum = "ec3672c180e71eeaaac3a541fbbc5f5ad4def8b747c595ad30d674e43049f7b0" dependencies = [ "ab_glyph_rasterizer", "owned_ttf_parser", @@ -20,9 +20,9 @@ checksum = "c71b1793ee61086797f5c80b6efa2b8ffa6d5dd703f118545808a7f2e27f7046" [[package]] name = "addr2line" -version = "0.24.1" +version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5fb1d8e4442bd405fdfd1dacb42792696b0cf9cb15882e5d097b742a676d375" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" dependencies = [ "gimli", ] @@ -152,7 +152,7 @@ checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -287,7 +287,7 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -316,13 +316,13 @@ checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" [[package]] name = "async-trait" -version = "0.1.82" +version = "0.1.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a27b8a3a6e1a44fa4c8baf1f653e4172e81486d4941f2237e20dc2d0cf4ddff1" +checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -333,9 +333,9 @@ checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "autocfg" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "av1-grain" @@ -501,13 +501,13 @@ dependencies = [ [[package]] name = "bytemuck_derive" -version = "1.7.1" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cc8b54b395f2fcfbb3d90c47b01c7f444d94d05bdeb775811dec868ac3bbc26" +checksum = "bcfcc3cd946cb52f0bbfdbbcfa2f4e24f75ebb6c0e1002f7c25904fada18b9ec" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -556,9 +556,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.1.21" +version = "1.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07b1695e2c7e8fc85310cde85aeaab7e3097f593c91d209d3f9df76c928100f0" +checksum = "2e80e3b6a3ab07840e1cae9b0666a63970dc28e8ed5ffbcdacbfc760c281bfc1" dependencies = [ "jobserver", "libc", @@ -1105,7 +1105,7 @@ checksum = "de0d48a183585823424a4ce1aa132d174a6a81bd540895822eb4c8373a8e49e8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -1200,9 +1200,9 @@ checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" [[package]] name = "fdeflate" -version = "0.3.4" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f9bfee30e4dedf0ab8b422f03af778d9612b63f502710fc500a334ebe2de645" +checksum = "d8090f921a24b04994d9929e204f50b498a33ea6ba559ffaa05e04f7ee7fb5ab" dependencies = [ "simd-adler32", ] @@ -1232,9 +1232,9 @@ dependencies = [ [[package]] name = "flate2" -version = "1.0.33" +version = "1.0.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "324a1be68054ef05ad64b861cc9eaf1d623d2d8cb25b4bf2cb9cdd902b4bf253" +checksum = "a1b589b4dc103969ad3cf85c950899926ec64300a1a46d76c03a6072957036f0" dependencies = [ "crc32fast", "miniz_oxide 0.8.0", @@ -1263,9 +1263,9 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "font-types" -version = "0.6.0" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f0189ccb084f77c5523e08288d418cbaa09c451a08515678a0aa265df9a8b60" +checksum = "dda6e36206148f69fc6ecb1bb6c0dedd7ee469f3db1d0dc2045beea28430ca43" dependencies = [ "bytemuck", ] @@ -1320,7 +1320,7 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -1346,9 +1346,9 @@ dependencies = [ [[package]] name = "futures" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" dependencies = [ "futures-channel", "futures-core", @@ -1361,9 +1361,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ "futures-core", "futures-sink", @@ -1371,15 +1371,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" [[package]] name = "futures-executor" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" dependencies = [ "futures-core", "futures-task", @@ -1389,9 +1389,9 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" [[package]] name = "futures-lite" @@ -1408,32 +1408,32 @@ dependencies = [ [[package]] name = "futures-macro" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] name = "futures-sink" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" [[package]] name = "futures-task" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" [[package]] name = "futures-util" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ "futures-channel", "futures-core", @@ -1490,9 +1490,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.31.0" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32085ea23f3234fc7846555e85283ba4de91e21016dc0455a16286d87a292d64" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] name = "gl_generator" @@ -1642,6 +1642,12 @@ dependencies = [ "allocator-api2", ] +[[package]] +name = "hashbrown" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" + [[package]] name = "hassle-rs" version = "0.11.0" @@ -1729,9 +1735,9 @@ dependencies = [ [[package]] name = "httparse" -version = "1.9.4" +version = "1.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9" +checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" [[package]] name = "hyper" @@ -1789,9 +1795,9 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da62f120a8a37763efb0cf8fdf264b884c7b8b9ac8660b900c8661030c00e6ba" +checksum = "41296eb09f183ac68eec06e03cdbea2e759633d4067b2f6552fc2e009bcad08b" dependencies = [ "bytes", "futures-channel", @@ -1802,7 +1808,6 @@ dependencies = [ "pin-project-lite", "socket2", "tokio", - "tower", "tower-service", "tracing", ] @@ -1880,6 +1885,14 @@ dependencies = [ "web-time", ] +[[package]] +name = "iced_event_wrapper" +version = "0.1.0" +source = "git+https://github.com/LegitCamper/iced_event_wrapper#94095398d91e7b5e8016f72bb6d60dd57228ffbe" +dependencies = [ + "iced", +] + [[package]] name = "iced_fonts" version = "0.1.1" @@ -1993,9 +2006,9 @@ dependencies = [ [[package]] name = "iced_wgpu" -version = "0.13.0" +version = "0.13.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8194d7666004b8e947f89ab7446d323ab0b882971226c1913ef98764c9e4bbc4" +checksum = "15708887133671d2bcc6c1d01d1f176f43a64d6cdc3b2bf893396c3ee498295f" dependencies = [ "bitflags 2.6.0", "bytemuck", @@ -2055,6 +2068,7 @@ dependencies = [ "clipboard-rs", "iced", "iced_aw", + "iced_event_wrapper", "iced_fonts", "iced_on_focus_widget", "rand", @@ -2138,12 +2152,12 @@ checksum = "44feda355f4159a7c757171a77de25daf6411e217b4cabd03bd6650690468126" [[package]] name = "indexmap" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5" +checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" dependencies = [ "equivalent", - "hashbrown 0.14.5", + "hashbrown 0.15.0", ] [[package]] @@ -2163,14 +2177,14 @@ checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] name = "ipnet" -version = "2.10.0" +version = "2.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "187674a687eed5fe42285b40c6291f9a01517d415fad1c3cbc6a9f778af7fcd4" +checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708" [[package]] name = "itertools" @@ -2238,9 +2252,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.70" +version = "0.3.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a" +checksum = "0cb94a0ffd3f3ee755c20f7d8752f45cac88605a4dcf808abcff72873296ec7b" dependencies = [ "wasm-bindgen", ] @@ -2295,9 +2309,9 @@ checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" [[package]] name = "libc" -version = "0.2.158" +version = "0.2.159" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" +checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" [[package]] name = "libfuzzer-sys" @@ -2355,7 +2369,7 @@ checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ "bitflags 2.6.0", "libc", - "redox_syscall 0.5.4", + "redox_syscall 0.5.7", ] [[package]] @@ -2397,9 +2411,9 @@ dependencies = [ [[package]] name = "lru" -version = "0.12.4" +version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37ee39891760e7d94734f6f63fedc29a2e4a152f836120753a72503f09fcf904" +checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" [[package]] name = "lyon" @@ -2538,7 +2552,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" dependencies = [ "adler", - "simd-adler32", ] [[package]] @@ -2548,6 +2561,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" dependencies = [ "adler2", + "simd-adler32", ] [[package]] @@ -2713,7 +2727,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -2784,7 +2798,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -3011,18 +3025,18 @@ dependencies = [ [[package]] name = "object" -version = "0.36.4" +version = "0.36.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "084f1a5821ac4c651660a94a7153d27ac9d8a53736203f58b31945ded098070a" +checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" dependencies = [ "memchr", ] [[package]] name = "once_cell" -version = "1.19.0" +version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" [[package]] name = "openssl" @@ -3047,7 +3061,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -3119,16 +3133,16 @@ dependencies = [ "proc-macro2", "proc-macro2-diagnostics", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] name = "owned_ttf_parser" -version = "0.24.0" +version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "490d3a563d3122bf7c911a59b0add9389e5ec0f5f0c3ac6b91ff235a0e6a7f90" +checksum = "22ec719bbf3b2a81c109a4e20b1f129b5566b7dce654bc3872f6a05abf82b2c4" dependencies = [ - "ttf-parser 0.24.1", + "ttf-parser 0.25.0", ] [[package]] @@ -3152,7 +3166,7 @@ dependencies = [ "by_address", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -3204,7 +3218,7 @@ checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.5.4", + "redox_syscall 0.5.7", "smallvec", "windows-targets 0.52.6", ] @@ -3251,7 +3265,7 @@ dependencies = [ "phf_shared", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -3265,22 +3279,22 @@ dependencies = [ [[package]] name = "pin-project" -version = "1.1.5" +version = "1.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" +checksum = "baf123a161dde1e524adf36f90bc5d8d3462824a9c43553ad07a8183161189ec" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.5" +version = "1.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" +checksum = "a4502d8515ca9f32f1fb543d987f63d95a14934883db45bdb48060b6b69257f8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -3308,21 +3322,21 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" +checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" [[package]] name = "png" -version = "0.17.13" +version = "0.17.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06e4b0d3d1312775e782c86c91a111aa1f910cbb65e1337f9975b5f9a554b5e1" +checksum = "52f9d46a34a05a6a57566bc2bfae066ef07585a6e3fa30fbbdff5936380623f0" dependencies = [ "bitflags 1.3.2", "crc32fast", "fdeflate", "flate2", - "miniz_oxide 0.7.4", + "miniz_oxide 0.8.0", ] [[package]] @@ -3372,9 +3386,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.86" +version = "1.0.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +checksum = "b3e4daa0dcf6feba26f985457cdf104d4b4256fc5a09547140f3631bb076b19a" dependencies = [ "unicode-ident", ] @@ -3387,7 +3401,7 @@ checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", "version_check", "yansi", ] @@ -3408,7 +3422,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8021cf59c8ec9c432cfc2526ac6b8aa508ecaf29cd415f271b8406c1b851c3fd" dependencies = [ "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -3611,9 +3625,9 @@ dependencies = [ [[package]] name = "read-fonts" -version = "0.20.0" +version = "0.22.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c141b9980e1150201b2a3a32879001c8f975fe313ec3df5471a9b5c79a880cd" +checksum = "fb94d9ac780fdcf9b6b252253f7d8f221379b84bd3573131139b383df69f85e1" dependencies = [ "bytemuck", "font-types", @@ -3639,9 +3653,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.4" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0884ad60e090bf1345b93da0a5de8923c93884cd03f40dfcfddd3b4bee661853" +checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" dependencies = [ "bitflags 2.6.0", ] @@ -3665,9 +3679,9 @@ checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832" [[package]] name = "reqwest" -version = "0.12.7" +version = "0.12.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8f4955649ef5c38cc7f9e8aa41761d48fb9677197daea9984dc54f56aad5e63" +checksum = "f713147fbe92361e52392c73b8c9e48c04c6625bce969ef54dc901e58e042a7b" dependencies = [ "base64", "bytes", @@ -3784,9 +3798,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.13" +version = "0.23.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2dabaac7466917e566adb06783a81ca48944c6898a1b08b9374106dd671f4c8" +checksum = "415d9944693cb90382053259f89fbb077ea730ad7273047ec63b19bc9b160ba8" dependencies = [ "log", "once_cell", @@ -3799,19 +3813,18 @@ dependencies = [ [[package]] name = "rustls-pemfile" -version = "2.1.3" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "196fe16b00e106300d3e45ecfcb764fa292a535d7326a29a5875c579c7417425" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" dependencies = [ - "base64", "rustls-pki-types", ] [[package]] name = "rustls-pki-types" -version = "1.8.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc0a2ce646f8655401bb81e7927b812614bd5d91dbc968696be50603510fcaf0" +checksum = "0e696e35370c65c9c541198af4543ccd580cf17fc25d8e05c5a242b202488c55" [[package]] name = "rustls-webpki" @@ -3864,9 +3877,9 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.24" +version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9aaafd5a2b6e3d657ff009d82fbd630b6bd54dd4eb06f21693925cdf80f9b8b" +checksum = "01227be5826fa0690321a2ba6c5cd57a19cf3f6a09e76973b58e61de6ab9d1c1" dependencies = [ "windows-sys 0.59.0", ] @@ -3942,7 +3955,7 @@ checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -3965,7 +3978,7 @@ checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -4066,9 +4079,9 @@ checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" [[package]] name = "skrifa" -version = "0.20.0" +version = "0.22.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abea4738067b1e628c6ce28b2c216c19e9ea95715cdb332680e821c3bec2ef23" +checksum = "8e1c44ad1f6c5bdd4eefed8326711b7dbda9ea45dfd36068c427d332aa382cbe" dependencies = [ "bytemuck", "read-fonts", @@ -4173,7 +4186,7 @@ dependencies = [ "objc2-foundation", "objc2-quartz-core", "raw-window-handle", - "redox_syscall 0.5.4", + "redox_syscall 0.5.7", "rustix", "tiny-xlib", "wasm-bindgen", @@ -4234,7 +4247,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -4251,9 +4264,9 @@ checksum = "20e16a0f46cf5fd675563ef54f26e83e20f2366bcf027bcb3cc3ed2b98aaf2ca" [[package]] name = "swash" -version = "0.1.18" +version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93cdc334a50fcc2aa3f04761af3b28196280a6aaadb1ef11215c478ae32615ac" +checksum = "cbd59f3f359ddd2c95af4758c18270eddd9c730dde98598023cdabff472c2ca2" dependencies = [ "skrifa", "yazi", @@ -4273,9 +4286,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.77" +version = "2.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" +checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590" dependencies = [ "proc-macro2", "quote", @@ -4342,9 +4355,9 @@ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" [[package]] name = "tempfile" -version = "3.12.0" +version = "3.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04cbcdd0c794ebb0d4cf35e88edd2f7d2c4c3e9a5a6dab322839b321c6a87a64" +checksum = "f0f2c9fc62d0beef6951ccffd757e241266a2c833136efbe35af6cd2567dca5b" dependencies = [ "cfg-if", "fastrand", @@ -4379,7 +4392,7 @@ checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -4549,9 +4562,9 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.22.21" +version = "0.22.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b072cee73c449a636ffd6f32bd8de3a9f7119139aff882f44943ce2986dc5cf" +checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" dependencies = [ "indexmap", "serde", @@ -4560,27 +4573,6 @@ dependencies = [ "winnow", ] -[[package]] -name = "tower" -version = "0.4.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" -dependencies = [ - "futures-core", - "futures-util", - "pin-project", - "pin-project-lite", - "tokio", - "tower-layer", - "tower-service", -] - -[[package]] -name = "tower-layer" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" - [[package]] name = "tower-service" version = "0.3.3" @@ -4606,7 +4598,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -4638,9 +4630,9 @@ checksum = "2c591d83f69777866b9126b24c6dd9a18351f177e49d625920d19f989fd31cf8" [[package]] name = "ttf-parser" -version = "0.24.1" +version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5be21190ff5d38e8b4a2d3b6a3ae57f612cc39c96e83cedeaf7abc338a8bac4a" +checksum = "5902c5d130972a0000f60860bfbf46f7ca3db5391eddfedd1b8728bd9dc96c0e" [[package]] name = "typenum" @@ -4683,9 +4675,9 @@ dependencies = [ [[package]] name = "unicode-bidi" -version = "0.3.15" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" +checksum = "5ab17db44d7388991a428b2ee655ce0c212e862eff1768a455c58f9aad6e7893" [[package]] name = "unicode-bidi-mirroring" @@ -4722,9 +4714,9 @@ dependencies = [ [[package]] name = "unicode-properties" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52ea75f83c0137a9b98608359a5f1af8144876eb67bcb1ce837368e906a9f524" +checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0" [[package]] name = "unicode-script" @@ -4839,9 +4831,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.93" +version = "0.2.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" +checksum = "ef073ced962d62984fb38a36e5fdc1a2b23c9e0e1fa0689bb97afa4202ef6887" dependencies = [ "cfg-if", "once_cell", @@ -4850,24 +4842,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.93" +version = "0.2.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" +checksum = "c4bfab14ef75323f4eb75fa52ee0a3fb59611977fd3240da19b2cf36ff85030e" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.43" +version = "0.4.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61e9300f63a621e96ed275155c108eb6f843b6a26d053f122ab69724559dc8ed" +checksum = "65471f79c1022ffa5291d33520cbbb53b7687b01c2f8e83b57d102eed7ed479d" dependencies = [ "cfg-if", "js-sys", @@ -4877,9 +4869,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.93" +version = "0.2.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" +checksum = "a7bec9830f60924d9ceb3ef99d55c155be8afa76954edffbb5936ff4509474e7" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -4887,22 +4879,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.93" +version = "0.2.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" +checksum = "4c74f6e152a76a2ad448e223b0fc0b6b5747649c3d769cc6bf45737bf97d0ed6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.93" +version = "0.2.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" +checksum = "a42f6c679374623f295a8623adfe63d9284091245c3504bde47c17a3ce2777d9" [[package]] name = "wasm-timer" @@ -5030,9 +5022,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.70" +version = "0.3.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26fdeaafd9bd129f65e7c031593c24d62186301e0c72c8978fa1678be7d532c0" +checksum = "44188d185b5bdcae1052d08bcbcf9091a5524038d4572cc4f4f2bb9d5554ddd9" dependencies = [ "js-sys", "wasm-bindgen", @@ -5538,9 +5530,9 @@ dependencies = [ [[package]] name = "winnow" -version = "0.6.18" +version = "0.6.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68a9bda4691f099d435ad181000724da8e5899daa10713c2d432552b9ccd3a6f" +checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" dependencies = [ "memchr", ] @@ -5686,7 +5678,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", "zvariant_utils", ] @@ -5725,7 +5717,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -5780,7 +5772,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", "zvariant_utils", ] @@ -5792,5 +5784,5 @@ checksum = "c51bcff7cc3dbb5055396bcf774748c3dab426b4b8659046963523cee4808340" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] diff --git a/Cargo.toml b/Cargo.toml index 1eead15..1f68a68 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,6 +36,7 @@ iced = { version = "0.13", features = ["advanced", "image", "tokio", "lazy"] } iced_aw = { version = "0.10", features = ["tab_bar", "selection_list"] } iced_fonts = { version = "0.1.1", features = ["bootstrap"] } iced_on_focus_widget = "0.1.1" +iced_event_wrapper = { git = "https://github.com/LegitCamper/iced_event_wrapper" } rand = "0.8.5" reqwest = "0.12.5" serde = "1.0.207" diff --git a/src/lib.rs b/src/lib.rs index a14bb4f..5ad9815 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,7 +8,7 @@ pub use engines::{BrowserEngine, PixelFormat, Tab, TabInfo, Tabs}; pub use engines::ultralight::Ultralight; pub mod widgets; -pub use widgets::{browser_view, command_window, nav_bar, tab_bar, IcyBrowser, Message}; +pub use widgets::{browser_view, command_palatte, nav_bar, tab_bar, IcyBrowser, Message}; mod helpers; pub use helpers::{get_fonts, to_url, Bookmark, Bookmarks, ImageInfo}; diff --git a/src/widgets/command_window.rs b/src/widgets/command_window.rs index cfa2bd3..b09d457 100644 --- a/src/widgets/command_window.rs +++ b/src/widgets/command_window.rs @@ -1,11 +1,26 @@ use iced::widget::{center, column, container, mouse_area, opaque, stack, text_input}; +use iced::widget::{scrollable, text, Column}; use iced::{border, Color, Element, Length, Theme}; +use iced_event_wrapper::wrapper; +use strum_macros::Display; use super::Message; use crate::Bookmark; -mod results; -pub use results::{results_list, ResultType, ResultsList}; +#[derive(Clone, Debug, Display, PartialEq)] +pub enum ResultType { + Commands(Message), + Bookmarks(Bookmark), +} + +impl ResultType { + pub fn inner_name(&self) -> String { + match self { + ResultType::Commands(command) => command.to_string(), + ResultType::Bookmarks(bookmark) => format!("Go to: {}", bookmark.url()), + } + } +} pub struct CommandWindowState { pub query: String, @@ -44,6 +59,54 @@ impl CommandWindowState { selected_item: None, } } + + pub fn next_item(&mut self) { + match &self.selected_item { + None => { + self.selected_item = self + .filtered_results + .get(0) + .map(|res| res.inner_name()) + .or_else(|| None) + } + Some(selected_item) => { + if let Some(last) = self.filtered_results.last() { + if *selected_item != last.inner_name() { + let pos = self + .filtered_results + .iter() + .position(|res| res.inner_name() == *selected_item) + .unwrap(); + self.selected_item = Some(self.filtered_results[pos + 1].inner_name()); + } + } + } + } + } + + pub fn previous_item(&mut self) { + match &self.selected_item { + None => { + self.selected_item = self + .filtered_results + .get(0) + .map(|res| res.inner_name()) + .or_else(|| None) + } + Some(selected_item) => { + if let Some(first) = self.filtered_results.first() { + if *selected_item != first.inner_name() { + let pos = self + .filtered_results + .iter() + .position(|res| res.inner_name() == *selected_item) + .unwrap(); + self.selected_item = Some(self.filtered_results[pos - 1].inner_name()); + } + } + } + } + } } impl Default for CommandWindowState { @@ -52,31 +115,30 @@ impl Default for CommandWindowState { } } -pub fn command_window<'a>( +pub fn command_palatte<'a>( base: impl Into>, - state: &CommandWindowState, + state: &'a CommandWindowState, ) -> Element<'a, Message> { - let window: iced::widget::Container<'_, super::Message, Theme, iced::Renderer> = - container(column![ - text_input("Command Menu", &state.query) - .on_input(Message::CommandPalatteQueryChanged) - .size(25), - container(results_list( - state.filtered_results.as_slice(), - state.selected_item.as_ref(), - )) - .width(Length::Fill) - .height(Length::Fill) - ]) - .padding(10) - .center(600) - .style(|theme: &Theme| container::Style { - background: Some(theme.palette().background.into()), - border: border::rounded(10), - ..container::Style::default() - }); - - stack![ + let window = container(column![ + text_input("Command Menu", &state.query) + .on_input(Message::CommandPalatteQueryChanged) + .size(25), + container(results_list( + state.filtered_results.as_slice(), + state.selected_item.clone(), + )) + .width(Length::Fill) + .height(Length::Fill) + ]) + .padding(10) + .center(600) + .style(|theme: &Theme| container::Style { + background: Some(theme.palette().background.into()), + border: border::rounded(10), + ..container::Style::default() + }); + + let stack = stack![ base.into(), opaque( mouse_area(center(opaque(window)).style(|_theme| { @@ -93,6 +155,39 @@ pub fn command_window<'a>( })) .on_press(Message::HideOverlay), ) - ] - .into() + ]; + + wrapper(stack) + .on_keyboard_event(|event| Message::CommandPalatteKeyboardEvent(Some(event))) + .into() +} + +fn results_list<'a>( + results: &'a [ResultType], + selected_item: Option, +) -> Element<'a, Message> { + let mut list = Vec::new(); + let mut result_types = Vec::new(); + + for result in results { + if !result_types.contains(&result.to_string()) { + result_types.push(result.to_string()); + list.push(text(result.to_string()).size(20).into()) + } + + let mut text = text(format!(" {}", result.inner_name())).size(15); + if let Some(selected_item) = selected_item.as_ref() { + if result.inner_name() == *selected_item { + // highlight currently selected element + // text = text.color(iced::Color::new(50., 138., 176., 100.)); + text = text.size(30) + } + } + list.push(text.into()) + } + + scrollable(Column::from_vec(list)) + .width(Length::Fill) + .spacing(10) + .into() } diff --git a/src/widgets/command_window/results.rs b/src/widgets/command_window/results.rs deleted file mode 100644 index a2eb80e..0000000 --- a/src/widgets/command_window/results.rs +++ /dev/null @@ -1,117 +0,0 @@ -use iced::advanced::layout::{self, Layout}; -use iced::advanced::renderer; -use iced::advanced::widget::{self, Widget}; -use iced::widget::{scrollable, text, Column}; -use iced::{Element, Length, Size}; -use strum_macros::Display; - -use super::super::{Bookmark, Message}; - -#[derive(Clone, Display, PartialEq)] -pub enum ResultType { - Commands(Message), - Bookmarks(Bookmark), -} - -impl ResultType { - pub fn inner_name(&self) -> String { - match self { - ResultType::Commands(command) => command.to_string(), - ResultType::Bookmarks(bookmark) => format!("Go to: {}", bookmark.url()), - } - } -} - -pub fn results_list<'a>( - results: &'a [ResultType], - selected_item: Option<&'a String>, -) -> ResultsList<'a> { - ResultsList { - results, - selected_item, - } -} - -pub struct ResultsList<'a> { - results: &'a [ResultType], - selected_item: Option<&'a String>, -} - -impl<'a> ResultsList<'a> { - pub fn new(results: &'a [ResultType], selected_item: Option<&'a String>) -> Self { - ResultsList { - results, - selected_item, - } - } -} - -impl Widget for ResultsList<'_> -where - Renderer: renderer::Renderer + iced::advanced::text::Renderer, - Theme: iced::widget::scrollable::Catalog + iced::widget::text::Catalog, -{ - fn size(&self) -> Size { - Size { - width: Length::Fill, - height: Length::Fill, - } - } - - fn layout( - &self, - _tree: &mut widget::Tree, - _renderer: &Renderer, - limits: &layout::Limits, - ) -> layout::Node { - layout::Node::new(limits.max()) - } - - fn draw( - &self, - tree: &widget::Tree, - renderer: &mut Renderer, - theme: &Theme, - style: &renderer::Style, - layout: Layout<'_>, - cursor: iced::advanced::mouse::Cursor, - viewport: &iced::Rectangle, - ) { - let mut list: Vec> = Vec::new(); - let mut result_types = Vec::new(); - - for result in self.results { - if !result_types.contains(&result.to_string()) { - result_types.push(result.to_string()); - list.push(text(result.to_string()).size(20).into()) - } - let result_text = match result { - ResultType::Commands(command) => command.to_string(), - ResultType::Bookmarks(bookmark) => bookmark.to_string(), - }; - - let mut text = text(format!(" {}", result_text)).size(15); - if let Some(selected_item) = self.selected_item { - if result_text == *selected_item { - // text. - } - } - list.push(text.into()) - } - - scrollable(Column::from_vec(list)) - .width(Length::Fill) - .spacing(10) - .draw(tree, renderer, theme, style, layout, cursor, viewport) - } -} - -impl<'a, Message, Theme, Renderer> From> for Element<'a, Message, Theme, Renderer> -where - Renderer: renderer::Renderer + iced::advanced::text::Renderer, - Theme: iced::widget::scrollable::Catalog + iced::widget::text::Catalog, -{ - fn from(results_list: ResultsList) -> Self { - Self::new(results_list) - } -} diff --git a/src/widgets/mod.rs b/src/widgets/mod.rs index f1d1613..4fe8743 100644 --- a/src/widgets/mod.rs +++ b/src/widgets/mod.rs @@ -22,7 +22,7 @@ pub mod bookmark_bar; pub use bookmark_bar::bookmark_bar; pub mod command_window; -pub use command_window::{command_window, ResultType, ResultsList}; +pub use command_window::{command_palatte, ResultType}; use crate::{ engines::BrowserEngine, shortcut::check_shortcut, to_url, Bookmark, Bookmarks, ImageInfo, @@ -75,7 +75,7 @@ pub enum Message { UrlChanged(String), UpdateUrl, CommandPalatteQueryChanged(String), - CommandPalatteSelected(String), + CommandPalatteKeyboardEvent(Option), SendKeyboardEvent(Option), SendMouseEvent(Point, Option), UpdateViewSize(Size), @@ -336,9 +336,32 @@ impl IcyBrowser { .collect(); Task::none() } - Message::CommandPalatteSelected(item) => { - self.command_window_state.selected_item = Some(item); - Task::none() + Message::CommandPalatteKeyboardEvent(event) => { + if let Some(keyboard::Event::KeyPressed { + key, + modified_key: _, + physical_key: _, + location: _, + modifiers: _, + text: _, + }) = event + { + match key { + key::Key::Named(key::Named::ArrowDown) => { + self.command_window_state.next_item(); + Task::none() + } + key::Key::Named(key::Named::ArrowUp) => { + self.command_window_state.previous_item(); + Task::none() + } + // key::Key::Character(_) => todo!(), + // key::Key::Unidentified => todo!(), + _ => Task::none(), + } + } else { + Task::none() + } } Message::ToggleOverlay => { if self.show_overlay { @@ -421,7 +444,7 @@ impl IcyBrowser { !self.show_overlay, ); if self.show_overlay { - column = column.push(command_window(browser_view, &self.command_window_state)) + column = column.push(command_palatte(browser_view, &self.command_window_state)) } else { column = column.push(browser_view); } From 26f54e895763db642c0109df4a3d08efcd505e69 Mon Sep 17 00:00:00 2001 From: sawyer bristol Date: Thu, 10 Oct 2024 16:48:34 -0600 Subject: [PATCH 8/9] clippy --- src/widgets/command_window.rs | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/src/widgets/command_window.rs b/src/widgets/command_window.rs index b09d457..f24cdc6 100644 --- a/src/widgets/command_window.rs +++ b/src/widgets/command_window.rs @@ -42,14 +42,10 @@ impl CommandWindowState { Message::CloseCurrentTab, ] .into_iter() - .map(|msg| ResultType::Commands(msg)), + .map(ResultType::Commands), ); if let Some(bookmarks) = bookmarks { - results.extend( - bookmarks - .into_iter() - .map(|bookmark| ResultType::Bookmarks(bookmark)), - ); + results.extend(bookmarks.into_iter().map(ResultType::Bookmarks)); }; Self { @@ -65,9 +61,9 @@ impl CommandWindowState { None => { self.selected_item = self .filtered_results - .get(0) + .first() .map(|res| res.inner_name()) - .or_else(|| None) + .or(None) } Some(selected_item) => { if let Some(last) = self.filtered_results.last() { @@ -89,9 +85,9 @@ impl CommandWindowState { None => { self.selected_item = self .filtered_results - .get(0) + .first() .map(|res| res.inner_name()) - .or_else(|| None) + .or(None) } Some(selected_item) => { if let Some(first) = self.filtered_results.first() { @@ -162,10 +158,7 @@ pub fn command_palatte<'a>( .into() } -fn results_list<'a>( - results: &'a [ResultType], - selected_item: Option, -) -> Element<'a, Message> { +fn results_list<'a>(results: &[ResultType], selected_item: Option) -> Element<'a, Message> { let mut list = Vec::new(); let mut result_types = Vec::new(); From b486debe77cbbfde13e724cd3482829cf3c45e01 Mon Sep 17 00:00:00 2001 From: sawyer bristol Date: Thu, 10 Oct 2024 17:54:31 -0600 Subject: [PATCH 9/9] works almost like expected --- src/widgets/command_window.rs | 28 ++++++++------ src/widgets/mod.rs | 70 ++++++++++++++++++++++++++++++++++- 2 files changed, 85 insertions(+), 13 deletions(-) diff --git a/src/widgets/command_window.rs b/src/widgets/command_window.rs index f24cdc6..4ca9848 100644 --- a/src/widgets/command_window.rs +++ b/src/widgets/command_window.rs @@ -31,8 +31,8 @@ pub struct CommandWindowState { impl CommandWindowState { pub fn new(bookmarks: Option>) -> Self { - // This may need to be extended in the future let mut results: Vec = Vec::new(); + // This may need to be extended in the future results.extend( vec![ Message::GoBackward, @@ -68,12 +68,15 @@ impl CommandWindowState { Some(selected_item) => { if let Some(last) = self.filtered_results.last() { if *selected_item != last.inner_name() { - let pos = self + if let Some(pos) = self .filtered_results .iter() .position(|res| res.inner_name() == *selected_item) - .unwrap(); - self.selected_item = Some(self.filtered_results[pos + 1].inner_name()); + { + self.selected_item = Some(self.filtered_results[pos + 1].inner_name()); + } else { + self.selected_item = None + } } } } @@ -92,12 +95,15 @@ impl CommandWindowState { Some(selected_item) => { if let Some(first) = self.filtered_results.first() { if *selected_item != first.inner_name() { - let pos = self + if let Some(pos) = self .filtered_results .iter() .position(|res| res.inner_name() == *selected_item) - .unwrap(); - self.selected_item = Some(self.filtered_results[pos - 1].inner_name()); + { + self.selected_item = Some(self.filtered_results[pos - 1].inner_name()); + } else { + self.selected_item = None; + } } } } @@ -168,12 +174,12 @@ fn results_list<'a>(results: &[ResultType], selected_item: Option) -> El list.push(text(result.to_string()).size(20).into()) } - let mut text = text(format!(" {}", result.inner_name())).size(15); + let mut text = container(text(format!(" {}", result.inner_name())).size(16)); if let Some(selected_item) = selected_item.as_ref() { if result.inner_name() == *selected_item { - // highlight currently selected element - // text = text.color(iced::Color::new(50., 138., 176., 100.)); - text = text.size(30) + text = text.style(|theme: &Theme| { + container::Style::default().background(theme.palette().primary) + }) } } list.push(text.into()) diff --git a/src/widgets/mod.rs b/src/widgets/mod.rs index 4fe8743..5bb8103 100644 --- a/src/widgets/mod.rs +++ b/src/widgets/mod.rs @@ -347,6 +347,14 @@ impl IcyBrowser { }) = event { match key { + key::Key::Named(key::Named::Escape) => { + self.command_window_state.query = String::new(); + self.command_window_state.filtered_results = + self.command_window_state.possible_results.clone(); + self.command_window_state.selected_item = None; + + Task::done(Message::HideOverlay) + } key::Key::Named(key::Named::ArrowDown) => { self.command_window_state.next_item(); Task::none() @@ -355,8 +363,66 @@ impl IcyBrowser { self.command_window_state.previous_item(); Task::none() } - // key::Key::Character(_) => todo!(), - // key::Key::Unidentified => todo!(), + key::Key::Named(key::Named::Backspace) => { + self.command_window_state.next_item(); + if self.command_window_state.query.is_empty() { + Task::none() + } else { + Task::done(Message::CommandPalatteQueryChanged( + self.command_window_state.query + [..self.command_window_state.query.len() - 1] + .to_string(), + )) + } + } + key::Key::Named(key::Named::Space) => { + self.command_window_state.next_item(); + Task::done(Message::CommandPalatteQueryChanged(format!( + "{} ", + self.command_window_state.query + ))) + } + key::Key::Character(char) => { + self.command_window_state.next_item(); + Task::done(Message::CommandPalatteQueryChanged(format!( + "{}{}", + self.command_window_state.query, char + ))) + } + key::Key::Named(key::Named::Enter) => { + for result in &self.command_window_state.filtered_results { + if let Some(selected_item) = + &self.command_window_state.selected_item + { + if result.inner_name() == *selected_item { + let task = match result { + ResultType::Commands(message) => message.clone(), + ResultType::Bookmarks(bookmark) => { + Message::GoToUrl(bookmark.url().to_string()) + } + }; + + self.command_window_state.query = String::new(); + self.command_window_state.filtered_results = + self.command_window_state.possible_results.clone(); + self.command_window_state.selected_item = None; + + return Task::batch([ + Task::done(task), + Task::done(Message::HideOverlay), + ]); + } + } + } + // TODO: maybe make red to show none was selected + self.command_window_state.query = String::new(); + self.command_window_state.filtered_results = + self.command_window_state.possible_results.clone(); + self.command_window_state.selected_item = None; + + Task::done(Message::HideOverlay) + } + _ => Task::none(), } } else {