summaryrefslogtreecommitdiffstats
path: root/graphics
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--graphics/fonts/Icons.ttfbin4912 -> 5032 bytes
-rw-r--r--graphics/src/backend.rs7
-rw-r--r--graphics/src/font.rs6
-rw-r--r--graphics/src/lib.rs3
-rw-r--r--graphics/src/overlay.rs2
-rw-r--r--graphics/src/overlay/menu.rs108
-rw-r--r--graphics/src/renderer.rs33
-rw-r--r--graphics/src/widget.rs3
-rw-r--r--graphics/src/widget/pick_list.rs97
9 files changed, 256 insertions, 3 deletions
diff --git a/graphics/fonts/Icons.ttf b/graphics/fonts/Icons.ttf
index 1c832f86..5e455b69 100644
--- a/graphics/fonts/Icons.ttf
+++ b/graphics/fonts/Icons.ttf
Binary files differ
diff --git a/graphics/src/backend.rs b/graphics/src/backend.rs
index b73c636e..dd7dbbc2 100644
--- a/graphics/src/backend.rs
+++ b/graphics/src/backend.rs
@@ -22,9 +22,14 @@ pub trait Text {
/// The `char` representing a ✔ icon in the [`ICON_FONT`].
///
- /// [`ICON_FONT`]: #associatedconst.ICON_FONt
+ /// [`ICON_FONT`]: #associatedconst.ICON_FONT
const CHECKMARK_ICON: char;
+ /// The `char` representing a ▼ icon in the built-in [`ICONS`] font.
+ ///
+ /// [`ICON_FONT`]: #associatedconst.ICON_FONT
+ const ARROW_DOWN_ICON: char;
+
/// Returns the default size of text.
fn default_size(&self) -> u16;
diff --git a/graphics/src/font.rs b/graphics/src/font.rs
index bcc28857..5c62681c 100644
--- a/graphics/src/font.rs
+++ b/graphics/src/font.rs
@@ -31,3 +31,9 @@ pub const ICONS: iced_native::Font = iced_native::Font::External {
#[cfg(feature = "font-icons")]
#[cfg_attr(docsrs, doc(cfg(feature = "font-icons")))]
pub const CHECKMARK_ICON: char = '\u{F00C}';
+
+/// The `char` representing a ▼ icon in the built-in [`ICONS`] font.
+///
+/// [`ICONS`]: const.ICONS.html
+#[cfg(feature = "font-icons")]
+pub const ARROW_DOWN_ICON: char = '\u{E800}';
diff --git a/graphics/src/lib.rs b/graphics/src/lib.rs
index 38d8dffa..d03f3b48 100644
--- a/graphics/src/lib.rs
+++ b/graphics/src/lib.rs
@@ -13,13 +13,14 @@ mod primitive;
mod renderer;
mod transformation;
mod viewport;
-mod widget;
pub mod backend;
pub mod defaults;
pub mod font;
pub mod layer;
+pub mod overlay;
pub mod triangle;
+pub mod widget;
pub mod window;
#[doc(no_inline)]
diff --git a/graphics/src/overlay.rs b/graphics/src/overlay.rs
new file mode 100644
index 00000000..bc0ed744
--- /dev/null
+++ b/graphics/src/overlay.rs
@@ -0,0 +1,2 @@
+//! Display interactive elements on top of other widgets.
+pub mod menu;
diff --git a/graphics/src/overlay/menu.rs b/graphics/src/overlay/menu.rs
new file mode 100644
index 00000000..a952f065
--- /dev/null
+++ b/graphics/src/overlay/menu.rs
@@ -0,0 +1,108 @@
+//! Build and show dropdown menus.
+use crate::backend::{self, Backend};
+use crate::{Primitive, Renderer};
+use iced_native::{
+ mouse, overlay, Color, Font, HorizontalAlignment, Point, Rectangle,
+ VerticalAlignment,
+};
+
+pub use iced_style::menu::Style;
+
+impl<B> overlay::menu::Renderer for Renderer<B>
+where
+ B: Backend + backend::Text,
+{
+ type Style = Style;
+
+ fn decorate(
+ &mut self,
+ bounds: Rectangle,
+ _cursor_position: Point,
+ style: &Style,
+ (primitives, mouse_cursor): Self::Output,
+ ) -> Self::Output {
+ (
+ Primitive::Group {
+ primitives: vec![
+ Primitive::Quad {
+ bounds,
+ background: style.background,
+ border_color: style.border_color,
+ border_width: style.border_width,
+ border_radius: 0,
+ },
+ primitives,
+ ],
+ },
+ mouse_cursor,
+ )
+ }
+
+ fn draw<T: ToString>(
+ &mut self,
+ bounds: Rectangle,
+ cursor_position: Point,
+ options: &[T],
+ hovered_option: Option<usize>,
+ padding: u16,
+ text_size: u16,
+ font: Font,
+ style: &Style,
+ ) -> Self::Output {
+ use std::f32;
+
+ let is_mouse_over = bounds.contains(cursor_position);
+
+ let mut primitives = Vec::new();
+
+ for (i, option) in options.iter().enumerate() {
+ let is_selected = hovered_option == Some(i);
+
+ let bounds = Rectangle {
+ x: bounds.x,
+ y: bounds.y
+ + ((text_size as usize + padding as usize * 2) * i) as f32,
+ width: bounds.width,
+ height: f32::from(text_size + padding * 2),
+ };
+
+ if is_selected {
+ primitives.push(Primitive::Quad {
+ bounds,
+ background: style.selected_background,
+ border_color: Color::TRANSPARENT,
+ border_width: 0,
+ border_radius: 0,
+ });
+ }
+
+ primitives.push(Primitive::Text {
+ content: option.to_string(),
+ bounds: Rectangle {
+ x: bounds.x + f32::from(padding),
+ y: bounds.center_y(),
+ width: f32::INFINITY,
+ ..bounds
+ },
+ size: f32::from(text_size),
+ font,
+ color: if is_selected {
+ style.selected_text_color
+ } else {
+ style.text_color
+ },
+ horizontal_alignment: HorizontalAlignment::Left,
+ vertical_alignment: VerticalAlignment::Center,
+ });
+ }
+
+ (
+ Primitive::Group { primitives },
+ if is_mouse_over {
+ mouse::Interaction::Pointer
+ } else {
+ mouse::Interaction::default()
+ },
+ )
+ }
+}
diff --git a/graphics/src/renderer.rs b/graphics/src/renderer.rs
index c9360f3a..5d51e6d4 100644
--- a/graphics/src/renderer.rs
+++ b/graphics/src/renderer.rs
@@ -1,7 +1,9 @@
use crate::{Backend, Defaults, Primitive};
use iced_native::layout::{self, Layout};
use iced_native::mouse;
-use iced_native::{Background, Color, Element, Point, Widget};
+use iced_native::{
+ Background, Color, Element, Point, Rectangle, Vector, Widget,
+};
/// A backend-agnostic renderer that supports all the built-in widgets.
#[derive(Debug)]
@@ -53,6 +55,35 @@ where
layout
}
+
+ fn overlay(
+ &mut self,
+ (base_primitive, base_cursor): (Primitive, mouse::Interaction),
+ (overlay_primitives, overlay_cursor): (Primitive, mouse::Interaction),
+ overlay_bounds: Rectangle,
+ ) -> (Primitive, mouse::Interaction) {
+ (
+ Primitive::Group {
+ primitives: vec![
+ base_primitive,
+ Primitive::Clip {
+ bounds: Rectangle {
+ width: overlay_bounds.width + 0.5,
+ height: overlay_bounds.height + 0.5,
+ ..overlay_bounds
+ },
+ offset: Vector::new(0, 0),
+ content: Box::new(overlay_primitives),
+ },
+ ],
+ },
+ if base_cursor > overlay_cursor {
+ base_cursor
+ } else {
+ overlay_cursor
+ },
+ )
+ }
}
impl<B> layout::Debugger for Renderer<B>
diff --git a/graphics/src/widget.rs b/graphics/src/widget.rs
index 1f6d6559..94a65011 100644
--- a/graphics/src/widget.rs
+++ b/graphics/src/widget.rs
@@ -12,6 +12,7 @@ pub mod checkbox;
pub mod container;
pub mod image;
pub mod pane_grid;
+pub mod pick_list;
pub mod progress_bar;
pub mod radio;
pub mod scrollable;
@@ -33,6 +34,8 @@ pub use container::Container;
#[doc(no_inline)]
pub use pane_grid::PaneGrid;
#[doc(no_inline)]
+pub use pick_list::PickList;
+#[doc(no_inline)]
pub use progress_bar::ProgressBar;
#[doc(no_inline)]
pub use radio::Radio;
diff --git a/graphics/src/widget/pick_list.rs b/graphics/src/widget/pick_list.rs
new file mode 100644
index 00000000..f42a8707
--- /dev/null
+++ b/graphics/src/widget/pick_list.rs
@@ -0,0 +1,97 @@
+//! Display a dropdown list of selectable values.
+use crate::backend::{self, Backend};
+use crate::{Primitive, Renderer};
+use iced_native::{
+ mouse, Font, HorizontalAlignment, Point, Rectangle, VerticalAlignment,
+};
+use iced_style::menu;
+
+pub use iced_native::pick_list::State;
+pub use iced_style::pick_list::{Style, StyleSheet};
+
+/// A widget allowing the selection of a single value from a list of options.
+pub type PickList<'a, T, Message, Backend> =
+ iced_native::PickList<'a, T, Message, Renderer<Backend>>;
+
+impl<B> iced_native::pick_list::Renderer for Renderer<B>
+where
+ B: Backend + backend::Text,
+{
+ type Style = Box<dyn StyleSheet>;
+
+ const DEFAULT_PADDING: u16 = 5;
+
+ fn menu_style(style: &Box<dyn StyleSheet>) -> menu::Style {
+ style.menu()
+ }
+
+ fn draw(
+ &mut self,
+ bounds: Rectangle,
+ cursor_position: Point,
+ selected: Option<String>,
+ padding: u16,
+ text_size: u16,
+ font: Font,
+ style: &Box<dyn StyleSheet>,
+ ) -> Self::Output {
+ let is_mouse_over = bounds.contains(cursor_position);
+
+ let style = if is_mouse_over {
+ style.hovered()
+ } else {
+ style.active()
+ };
+
+ let background = Primitive::Quad {
+ bounds,
+ background: style.background,
+ border_color: style.border_color,
+ border_width: style.border_width,
+ border_radius: style.border_radius,
+ };
+
+ let arrow_down = Primitive::Text {
+ content: B::ARROW_DOWN_ICON.to_string(),
+ font: B::ICON_FONT,
+ size: bounds.height * style.icon_size,
+ bounds: Rectangle {
+ x: bounds.x + bounds.width - f32::from(padding) * 2.0,
+ y: bounds.center_y(),
+ ..bounds
+ },
+ color: style.text_color,
+ horizontal_alignment: HorizontalAlignment::Right,
+ vertical_alignment: VerticalAlignment::Center,
+ };
+
+ (
+ Primitive::Group {
+ primitives: if let Some(label) = selected {
+ let label = Primitive::Text {
+ content: label,
+ size: f32::from(text_size),
+ font,
+ color: style.text_color,
+ bounds: Rectangle {
+ x: bounds.x + f32::from(padding),
+ y: bounds.center_y(),
+ ..bounds
+ },
+ horizontal_alignment: HorizontalAlignment::Left,
+ vertical_alignment: VerticalAlignment::Center,
+ };
+
+ vec![background, label, arrow_down]
+ } else {
+ vec![background, arrow_down]
+ },
+ },
+ if is_mouse_over {
+ mouse::Interaction::Pointer
+ } else {
+ mouse::Interaction::default()
+ },
+ )
+ }
+}