diff options
Diffstat (limited to 'native/src/overlay/menu.rs')
-rw-r--r-- | native/src/overlay/menu.rs | 519 |
1 files changed, 0 insertions, 519 deletions
diff --git a/native/src/overlay/menu.rs b/native/src/overlay/menu.rs deleted file mode 100644 index bd58a122..00000000 --- a/native/src/overlay/menu.rs +++ /dev/null @@ -1,519 +0,0 @@ -//! Build and show dropdown menus. -use crate::alignment; -use crate::event::{self, Event}; -use crate::layout; -use crate::mouse; -use crate::overlay; -use crate::renderer; -use crate::text::{self, Text}; -use crate::touch; -use crate::widget::container::{self, Container}; -use crate::widget::scrollable::{self, Scrollable}; -use crate::widget::Tree; -use crate::{ - Clipboard, Color, Element, Layout, Length, Padding, Pixels, Point, - Rectangle, Shell, Size, Vector, Widget, -}; - -pub use iced_style::menu::{Appearance, StyleSheet}; - -/// A list of selectable options. -#[allow(missing_debug_implementations)] -pub struct Menu<'a, T, Renderer> -where - Renderer: text::Renderer, - Renderer::Theme: StyleSheet, -{ - state: &'a mut State, - options: &'a [T], - hovered_option: &'a mut Option<usize>, - last_selection: &'a mut Option<T>, - width: f32, - padding: Padding, - text_size: Option<f32>, - font: Option<Renderer::Font>, - style: <Renderer::Theme as StyleSheet>::Style, -} - -impl<'a, T, Renderer> Menu<'a, T, Renderer> -where - T: ToString + Clone, - Renderer: text::Renderer + 'a, - Renderer::Theme: - StyleSheet + container::StyleSheet + scrollable::StyleSheet, -{ - /// Creates a new [`Menu`] with the given [`State`], a list of options, and - /// the message to produced when an option is selected. - pub fn new( - state: &'a mut State, - options: &'a [T], - hovered_option: &'a mut Option<usize>, - last_selection: &'a mut Option<T>, - ) -> Self { - Menu { - state, - options, - hovered_option, - last_selection, - width: 0.0, - padding: Padding::ZERO, - text_size: None, - font: None, - style: Default::default(), - } - } - - /// Sets the width of the [`Menu`]. - pub fn width(mut self, width: f32) -> Self { - self.width = width; - self - } - - /// Sets the [`Padding`] of the [`Menu`]. - pub fn padding<P: Into<Padding>>(mut self, padding: P) -> Self { - self.padding = padding.into(); - self - } - - /// Sets the text size of the [`Menu`]. - pub fn text_size(mut self, text_size: impl Into<Pixels>) -> Self { - self.text_size = Some(text_size.into().0); - self - } - - /// Sets the font of the [`Menu`]. - pub fn font(mut self, font: impl Into<Renderer::Font>) -> Self { - self.font = Some(font.into()); - self - } - - /// Sets the style of the [`Menu`]. - pub fn style( - mut self, - style: impl Into<<Renderer::Theme as StyleSheet>::Style>, - ) -> Self { - self.style = style.into(); - self - } - - /// Turns the [`Menu`] into an overlay [`Element`] at the given target - /// position. - /// - /// The `target_height` will be used to display the menu either on top - /// of the target or under it, depending on the screen position and the - /// dimensions of the [`Menu`]. - pub fn overlay<Message: 'a>( - self, - position: Point, - target_height: f32, - ) -> overlay::Element<'a, Message, Renderer> { - overlay::Element::new( - position, - Box::new(Overlay::new(self, target_height)), - ) - } -} - -/// The local state of a [`Menu`]. -#[derive(Debug)] -pub struct State { - tree: Tree, -} - -impl State { - /// Creates a new [`State`] for a [`Menu`]. - pub fn new() -> Self { - Self { - tree: Tree::empty(), - } - } -} - -impl Default for State { - fn default() -> Self { - Self::new() - } -} - -struct Overlay<'a, Message, Renderer> -where - Renderer: crate::Renderer, - Renderer::Theme: StyleSheet + container::StyleSheet, -{ - state: &'a mut Tree, - container: Container<'a, Message, Renderer>, - width: f32, - target_height: f32, - style: <Renderer::Theme as StyleSheet>::Style, -} - -impl<'a, Message, Renderer> Overlay<'a, Message, Renderer> -where - Message: 'a, - Renderer: 'a, - Renderer: text::Renderer, - Renderer::Theme: - StyleSheet + container::StyleSheet + scrollable::StyleSheet, -{ - pub fn new<T>(menu: Menu<'a, T, Renderer>, target_height: f32) -> Self - where - T: Clone + ToString, - { - let Menu { - state, - options, - hovered_option, - last_selection, - width, - padding, - font, - text_size, - style, - } = menu; - - let container = Container::new(Scrollable::new(List { - options, - hovered_option, - last_selection, - font, - text_size, - padding, - style: style.clone(), - })); - - state.tree.diff(&container as &dyn Widget<_, _>); - - Self { - state: &mut state.tree, - container, - width, - target_height, - style, - } - } -} - -impl<'a, Message, Renderer> crate::Overlay<Message, Renderer> - for Overlay<'a, Message, Renderer> -where - Renderer: text::Renderer, - Renderer::Theme: StyleSheet + container::StyleSheet, -{ - fn layout( - &self, - renderer: &Renderer, - bounds: Size, - position: Point, - ) -> layout::Node { - let space_below = bounds.height - (position.y + self.target_height); - let space_above = position.y; - - let limits = layout::Limits::new( - Size::ZERO, - Size::new( - bounds.width - position.x, - if space_below > space_above { - space_below - } else { - space_above - }, - ), - ) - .width(self.width); - - let mut node = self.container.layout(renderer, &limits); - - node.move_to(if space_below > space_above { - position + Vector::new(0.0, self.target_height) - } else { - position - Vector::new(0.0, node.size().height) - }); - - node - } - - fn on_event( - &mut self, - event: Event, - layout: Layout<'_>, - cursor_position: Point, - renderer: &Renderer, - clipboard: &mut dyn Clipboard, - shell: &mut Shell<'_, Message>, - ) -> event::Status { - self.container.on_event( - self.state, - event, - layout, - cursor_position, - renderer, - clipboard, - shell, - ) - } - - fn mouse_interaction( - &self, - layout: Layout<'_>, - cursor_position: Point, - viewport: &Rectangle, - renderer: &Renderer, - ) -> mouse::Interaction { - self.container.mouse_interaction( - self.state, - layout, - cursor_position, - viewport, - renderer, - ) - } - - fn draw( - &self, - renderer: &mut Renderer, - theme: &Renderer::Theme, - style: &renderer::Style, - layout: Layout<'_>, - cursor_position: Point, - ) { - let appearance = theme.appearance(&self.style); - let bounds = layout.bounds(); - - renderer.fill_quad( - renderer::Quad { - bounds, - border_color: appearance.border_color, - border_width: appearance.border_width, - border_radius: appearance.border_radius.into(), - }, - appearance.background, - ); - - self.container.draw( - self.state, - renderer, - theme, - style, - layout, - cursor_position, - &bounds, - ); - } -} - -struct List<'a, T, Renderer> -where - Renderer: text::Renderer, - Renderer::Theme: StyleSheet, -{ - options: &'a [T], - hovered_option: &'a mut Option<usize>, - last_selection: &'a mut Option<T>, - padding: Padding, - text_size: Option<f32>, - font: Option<Renderer::Font>, - style: <Renderer::Theme as StyleSheet>::Style, -} - -impl<'a, T, Message, Renderer> Widget<Message, Renderer> - for List<'a, T, Renderer> -where - T: Clone + ToString, - Renderer: text::Renderer, - Renderer::Theme: StyleSheet, -{ - fn width(&self) -> Length { - Length::Fill - } - - fn height(&self) -> Length { - Length::Shrink - } - - fn layout( - &self, - renderer: &Renderer, - limits: &layout::Limits, - ) -> layout::Node { - use std::f32; - - let limits = limits.width(Length::Fill).height(Length::Shrink); - let text_size = - self.text_size.unwrap_or_else(|| renderer.default_size()); - - let size = { - let intrinsic = Size::new( - 0.0, - (text_size * 1.2 + self.padding.vertical()) - * self.options.len() as f32, - ); - - limits.resolve(intrinsic) - }; - - layout::Node::new(size) - } - - fn on_event( - &mut self, - _state: &mut Tree, - event: Event, - layout: Layout<'_>, - cursor_position: Point, - renderer: &Renderer, - _clipboard: &mut dyn Clipboard, - _shell: &mut Shell<'_, Message>, - ) -> event::Status { - match event { - Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => { - let bounds = layout.bounds(); - - if bounds.contains(cursor_position) { - if let Some(index) = *self.hovered_option { - if let Some(option) = self.options.get(index) { - *self.last_selection = Some(option.clone()); - } - } - } - } - Event::Mouse(mouse::Event::CursorMoved { .. }) => { - let bounds = layout.bounds(); - - if bounds.contains(cursor_position) { - let text_size = self - .text_size - .unwrap_or_else(|| renderer.default_size()); - - *self.hovered_option = Some( - ((cursor_position.y - bounds.y) - / (text_size * 1.2 + self.padding.vertical())) - as usize, - ); - } - } - Event::Touch(touch::Event::FingerPressed { .. }) => { - let bounds = layout.bounds(); - - if bounds.contains(cursor_position) { - let text_size = self - .text_size - .unwrap_or_else(|| renderer.default_size()); - - *self.hovered_option = Some( - ((cursor_position.y - bounds.y) - / (text_size * 1.2 + self.padding.vertical())) - as usize, - ); - - if let Some(index) = *self.hovered_option { - if let Some(option) = self.options.get(index) { - *self.last_selection = Some(option.clone()); - } - } - } - } - _ => {} - } - - event::Status::Ignored - } - - fn mouse_interaction( - &self, - _state: &Tree, - layout: Layout<'_>, - cursor_position: Point, - _viewport: &Rectangle, - _renderer: &Renderer, - ) -> mouse::Interaction { - let is_mouse_over = layout.bounds().contains(cursor_position); - - if is_mouse_over { - mouse::Interaction::Pointer - } else { - mouse::Interaction::default() - } - } - - fn draw( - &self, - _state: &Tree, - renderer: &mut Renderer, - theme: &Renderer::Theme, - _style: &renderer::Style, - layout: Layout<'_>, - _cursor_position: Point, - viewport: &Rectangle, - ) { - let appearance = theme.appearance(&self.style); - let bounds = layout.bounds(); - - let text_size = - self.text_size.unwrap_or_else(|| renderer.default_size()); - let option_height = - (text_size * 1.2 + self.padding.vertical()) as usize; - - let offset = viewport.y - bounds.y; - let start = (offset / option_height as f32) as usize; - let end = - ((offset + viewport.height) / option_height as f32).ceil() as usize; - - let visible_options = &self.options[start..end.min(self.options.len())]; - - for (i, option) in visible_options.iter().enumerate() { - let i = start + i; - let is_selected = *self.hovered_option == Some(i); - - let bounds = Rectangle { - x: bounds.x, - y: bounds.y + (option_height * i) as f32, - width: bounds.width, - height: text_size * 1.2 + self.padding.vertical(), - }; - - if is_selected { - renderer.fill_quad( - renderer::Quad { - bounds, - border_color: Color::TRANSPARENT, - border_width: 0.0, - border_radius: appearance.border_radius.into(), - }, - appearance.selected_background, - ); - } - - renderer.fill_text(Text { - content: &option.to_string(), - bounds: Rectangle { - x: bounds.x + self.padding.left, - y: bounds.center_y(), - width: f32::INFINITY, - ..bounds - }, - size: text_size, - font: self.font.unwrap_or_else(|| renderer.default_font()), - color: if is_selected { - appearance.selected_text_color - } else { - appearance.text_color - }, - horizontal_alignment: alignment::Horizontal::Left, - vertical_alignment: alignment::Vertical::Center, - }); - } - } -} - -impl<'a, T, Message, Renderer> From<List<'a, T, Renderer>> - for Element<'a, Message, Renderer> -where - T: ToString + Clone, - Message: 'a, - Renderer: 'a + text::Renderer, - Renderer::Theme: StyleSheet, -{ - fn from(list: List<'a, T, Renderer>) -> Self { - Element::new(list) - } -} |