Merge pull request #1315 from casperstorm/pure/tooltip
Implemented Tooltip as Pure
This commit is contained in:
commit
bba4cf323b
8 changed files with 487 additions and 90 deletions
|
|
@ -93,6 +93,7 @@ members = [
|
|||
"examples/pure/pick_list",
|
||||
"examples/pure/todos",
|
||||
"examples/pure/tour",
|
||||
"examples/pure/tooltip",
|
||||
"examples/websocket",
|
||||
]
|
||||
|
||||
|
|
|
|||
9
examples/pure/tooltip/Cargo.toml
Normal file
9
examples/pure/tooltip/Cargo.toml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
[package]
|
||||
name = "pure_tooltip"
|
||||
version = "0.1.0"
|
||||
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>", "Casper Rogild Storm"]
|
||||
edition = "2021"
|
||||
publish = false
|
||||
|
||||
[dependencies]
|
||||
iced = { path = "../../..", features = ["pure"] }
|
||||
93
examples/pure/tooltip/src/main.rs
Normal file
93
examples/pure/tooltip/src/main.rs
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
use iced::pure::{
|
||||
button, container, tooltip, widget::tooltip::Position, Element, Sandbox,
|
||||
};
|
||||
use iced::{Length, Settings};
|
||||
|
||||
pub fn main() -> iced::Result {
|
||||
Example::run(Settings::default())
|
||||
}
|
||||
|
||||
struct Example {
|
||||
position: Position,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
enum Message {
|
||||
ChangePosition,
|
||||
}
|
||||
|
||||
impl Sandbox for Example {
|
||||
type Message = Message;
|
||||
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
position: Position::Bottom,
|
||||
}
|
||||
}
|
||||
|
||||
fn title(&self) -> String {
|
||||
String::from("Tooltip - Iced")
|
||||
}
|
||||
|
||||
fn update(&mut self, message: Message) {
|
||||
match message {
|
||||
Message::ChangePosition => {
|
||||
let position = match &self.position {
|
||||
Position::FollowCursor => Position::Top,
|
||||
Position::Top => Position::Bottom,
|
||||
Position::Bottom => Position::Left,
|
||||
Position::Left => Position::Right,
|
||||
Position::Right => Position::FollowCursor,
|
||||
};
|
||||
|
||||
self.position = position
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn view(&self) -> Element<Message> {
|
||||
let tooltip = tooltip(
|
||||
button("Press to change position")
|
||||
.on_press(Message::ChangePosition),
|
||||
position_to_text(self.position),
|
||||
self.position,
|
||||
)
|
||||
.gap(10)
|
||||
.style(style::Tooltip);
|
||||
|
||||
container(tooltip)
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill)
|
||||
.center_x()
|
||||
.center_y()
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
fn position_to_text<'a>(position: Position) -> &'a str {
|
||||
match position {
|
||||
Position::FollowCursor => "Follow Cursor",
|
||||
Position::Top => "Top",
|
||||
Position::Bottom => "Bottom",
|
||||
Position::Left => "Left",
|
||||
Position::Right => "Right",
|
||||
}
|
||||
}
|
||||
|
||||
mod style {
|
||||
use iced::container;
|
||||
use iced::Color;
|
||||
|
||||
pub struct Tooltip;
|
||||
|
||||
impl container::StyleSheet for Tooltip {
|
||||
fn style(&self) -> container::Style {
|
||||
container::Style {
|
||||
text_color: Some(Color::from_rgb8(0xEE, 0xEE, 0xEE)),
|
||||
background: Some(Color::from_rgb(0.11, 0.42, 0.87).into()),
|
||||
border_radius: 12.0,
|
||||
..container::Style::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -98,6 +98,117 @@ pub enum Position {
|
|||
Right,
|
||||
}
|
||||
|
||||
/// Draws a [`Tooltip`].
|
||||
pub fn draw<Renderer: crate::Renderer>(
|
||||
renderer: &mut Renderer,
|
||||
inherited_style: &renderer::Style,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
viewport: &Rectangle,
|
||||
position: Position,
|
||||
gap: u16,
|
||||
padding: u16,
|
||||
style_sheet: &dyn container::StyleSheet,
|
||||
layout_text: impl FnOnce(&Renderer, &layout::Limits) -> layout::Node,
|
||||
draw_text: impl FnOnce(
|
||||
&mut Renderer,
|
||||
&renderer::Style,
|
||||
Layout<'_>,
|
||||
Point,
|
||||
&Rectangle,
|
||||
),
|
||||
) {
|
||||
let bounds = layout.bounds();
|
||||
|
||||
if bounds.contains(cursor_position) {
|
||||
let gap = f32::from(gap);
|
||||
let style = style_sheet.style();
|
||||
|
||||
let defaults = renderer::Style {
|
||||
text_color: style.text_color.unwrap_or(inherited_style.text_color),
|
||||
};
|
||||
|
||||
let text_layout = layout_text(
|
||||
renderer,
|
||||
&layout::Limits::new(Size::ZERO, viewport.size())
|
||||
.pad(Padding::new(padding)),
|
||||
);
|
||||
|
||||
let padding = f32::from(padding);
|
||||
let text_bounds = text_layout.bounds();
|
||||
let x_center = bounds.x + (bounds.width - text_bounds.width) / 2.0;
|
||||
let y_center = bounds.y + (bounds.height - text_bounds.height) / 2.0;
|
||||
|
||||
let mut tooltip_bounds = {
|
||||
let offset = match position {
|
||||
Position::Top => Vector::new(
|
||||
x_center,
|
||||
bounds.y - text_bounds.height - gap - padding,
|
||||
),
|
||||
Position::Bottom => Vector::new(
|
||||
x_center,
|
||||
bounds.y + bounds.height + gap + padding,
|
||||
),
|
||||
Position::Left => Vector::new(
|
||||
bounds.x - text_bounds.width - gap - padding,
|
||||
y_center,
|
||||
),
|
||||
Position::Right => Vector::new(
|
||||
bounds.x + bounds.width + gap + padding,
|
||||
y_center,
|
||||
),
|
||||
Position::FollowCursor => Vector::new(
|
||||
cursor_position.x,
|
||||
cursor_position.y - text_bounds.height,
|
||||
),
|
||||
};
|
||||
|
||||
Rectangle {
|
||||
x: offset.x - padding,
|
||||
y: offset.y - padding,
|
||||
width: text_bounds.width + padding * 2.0,
|
||||
height: text_bounds.height + padding * 2.0,
|
||||
}
|
||||
};
|
||||
|
||||
if tooltip_bounds.x < viewport.x {
|
||||
tooltip_bounds.x = viewport.x;
|
||||
} else if viewport.x + viewport.width
|
||||
< tooltip_bounds.x + tooltip_bounds.width
|
||||
{
|
||||
tooltip_bounds.x =
|
||||
viewport.x + viewport.width - tooltip_bounds.width;
|
||||
}
|
||||
|
||||
if tooltip_bounds.y < viewport.y {
|
||||
tooltip_bounds.y = viewport.y;
|
||||
} else if viewport.y + viewport.height
|
||||
< tooltip_bounds.y + tooltip_bounds.height
|
||||
{
|
||||
tooltip_bounds.y =
|
||||
viewport.y + viewport.height - tooltip_bounds.height;
|
||||
}
|
||||
|
||||
renderer.with_layer(*viewport, |renderer| {
|
||||
container::draw_background(renderer, &style, tooltip_bounds);
|
||||
|
||||
draw_text(
|
||||
renderer,
|
||||
&defaults,
|
||||
Layout::with_offset(
|
||||
Vector::new(
|
||||
tooltip_bounds.x + padding,
|
||||
tooltip_bounds.y + padding,
|
||||
),
|
||||
&text_layout,
|
||||
),
|
||||
cursor_position,
|
||||
viewport,
|
||||
)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Message, Renderer> Widget<Message, Renderer>
|
||||
for Tooltip<'a, Message, Renderer>
|
||||
where
|
||||
|
|
@ -169,100 +280,32 @@ where
|
|||
viewport,
|
||||
);
|
||||
|
||||
let bounds = layout.bounds();
|
||||
|
||||
if bounds.contains(cursor_position) {
|
||||
let gap = f32::from(self.gap);
|
||||
let style = self.style_sheet.style();
|
||||
|
||||
let defaults = renderer::Style {
|
||||
text_color: style
|
||||
.text_color
|
||||
.unwrap_or(inherited_style.text_color),
|
||||
};
|
||||
|
||||
let text_layout = Widget::<(), Renderer>::layout(
|
||||
&self.tooltip,
|
||||
renderer,
|
||||
&layout::Limits::new(Size::ZERO, viewport.size())
|
||||
.pad(Padding::new(self.padding)),
|
||||
);
|
||||
|
||||
let padding = f32::from(self.padding);
|
||||
let text_bounds = text_layout.bounds();
|
||||
let x_center = bounds.x + (bounds.width - text_bounds.width) / 2.0;
|
||||
let y_center =
|
||||
bounds.y + (bounds.height - text_bounds.height) / 2.0;
|
||||
|
||||
let mut tooltip_bounds = {
|
||||
let offset = match self.position {
|
||||
Position::Top => Vector::new(
|
||||
x_center,
|
||||
bounds.y - text_bounds.height - gap - padding,
|
||||
),
|
||||
Position::Bottom => Vector::new(
|
||||
x_center,
|
||||
bounds.y + bounds.height + gap + padding,
|
||||
),
|
||||
Position::Left => Vector::new(
|
||||
bounds.x - text_bounds.width - gap - padding,
|
||||
y_center,
|
||||
),
|
||||
Position::Right => Vector::new(
|
||||
bounds.x + bounds.width + gap + padding,
|
||||
y_center,
|
||||
),
|
||||
Position::FollowCursor => Vector::new(
|
||||
cursor_position.x,
|
||||
cursor_position.y - text_bounds.height,
|
||||
),
|
||||
};
|
||||
|
||||
Rectangle {
|
||||
x: offset.x - padding,
|
||||
y: offset.y - padding,
|
||||
width: text_bounds.width + padding * 2.0,
|
||||
height: text_bounds.height + padding * 2.0,
|
||||
}
|
||||
};
|
||||
|
||||
if tooltip_bounds.x < viewport.x {
|
||||
tooltip_bounds.x = viewport.x;
|
||||
} else if viewport.x + viewport.width
|
||||
< tooltip_bounds.x + tooltip_bounds.width
|
||||
{
|
||||
tooltip_bounds.x =
|
||||
viewport.x + viewport.width - tooltip_bounds.width;
|
||||
}
|
||||
|
||||
if tooltip_bounds.y < viewport.y {
|
||||
tooltip_bounds.y = viewport.y;
|
||||
} else if viewport.y + viewport.height
|
||||
< tooltip_bounds.y + tooltip_bounds.height
|
||||
{
|
||||
tooltip_bounds.y =
|
||||
viewport.y + viewport.height - tooltip_bounds.height;
|
||||
}
|
||||
|
||||
renderer.with_layer(*viewport, |renderer| {
|
||||
container::draw_background(renderer, &style, tooltip_bounds);
|
||||
let tooltip = &self.tooltip;
|
||||
|
||||
draw(
|
||||
renderer,
|
||||
inherited_style,
|
||||
layout,
|
||||
cursor_position,
|
||||
viewport,
|
||||
self.position,
|
||||
self.gap,
|
||||
self.padding,
|
||||
self.style_sheet.as_ref(),
|
||||
|renderer, limits| {
|
||||
Widget::<(), Renderer>::layout(tooltip, renderer, limits)
|
||||
},
|
||||
|renderer, defaults, layout, cursor_position, viewport| {
|
||||
Widget::<(), Renderer>::draw(
|
||||
&self.tooltip,
|
||||
tooltip,
|
||||
renderer,
|
||||
&defaults,
|
||||
Layout::with_offset(
|
||||
Vector::new(
|
||||
tooltip_bounds.x + padding,
|
||||
tooltip_bounds.y + padding,
|
||||
),
|
||||
&text_layout,
|
||||
),
|
||||
defaults,
|
||||
layout,
|
||||
cursor_position,
|
||||
viewport,
|
||||
);
|
||||
});
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -273,8 +316,8 @@ where
|
|||
Message: 'a,
|
||||
{
|
||||
fn from(
|
||||
column: Tooltip<'a, Message, Renderer>,
|
||||
tooltip: Tooltip<'a, Message, Renderer>,
|
||||
) -> Element<'a, Message, Renderer> {
|
||||
Element::new(column)
|
||||
Element::new(tooltip)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,6 +38,17 @@ pub fn button<'a, Message, Renderer>(
|
|||
widget::Button::new(content)
|
||||
}
|
||||
|
||||
pub fn tooltip<'a, Message, Renderer>(
|
||||
content: impl Into<Element<'a, Message, Renderer>>,
|
||||
tooltip: impl ToString,
|
||||
position: widget::tooltip::Position,
|
||||
) -> widget::Tooltip<'a, Message, Renderer>
|
||||
where
|
||||
Renderer: iced_native::text::Renderer,
|
||||
{
|
||||
widget::Tooltip::new(content, tooltip, position)
|
||||
}
|
||||
|
||||
pub fn text<Renderer>(text: impl Into<String>) -> widget::Text<Renderer>
|
||||
where
|
||||
Renderer: iced_native::text::Renderer,
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ pub mod slider;
|
|||
pub mod svg;
|
||||
pub mod text_input;
|
||||
pub mod toggler;
|
||||
pub mod tooltip;
|
||||
pub mod tree;
|
||||
|
||||
mod column;
|
||||
|
|
@ -37,6 +38,7 @@ pub use svg::Svg;
|
|||
pub use text::Text;
|
||||
pub use text_input::TextInput;
|
||||
pub use toggler::Toggler;
|
||||
pub use tooltip::{Position, Tooltip};
|
||||
pub use tree::Tree;
|
||||
|
||||
use iced_native::event::{self, Event};
|
||||
|
|
|
|||
228
pure/src/widget/tooltip.rs
Normal file
228
pure/src/widget/tooltip.rs
Normal file
|
|
@ -0,0 +1,228 @@
|
|||
//! Display a widget over another.
|
||||
use crate::widget::Tree;
|
||||
use crate::{Element, Widget};
|
||||
use iced_native::event::{self, Event};
|
||||
use iced_native::layout;
|
||||
use iced_native::mouse;
|
||||
use iced_native::overlay;
|
||||
use iced_native::renderer;
|
||||
use iced_native::text;
|
||||
use iced_native::widget::tooltip;
|
||||
use iced_native::widget::Text;
|
||||
use iced_native::{Clipboard, Layout, Length, Point, Rectangle, Shell};
|
||||
|
||||
pub use iced_style::container::{Style, StyleSheet};
|
||||
pub use tooltip::Position;
|
||||
|
||||
/// An element to display a widget over another.
|
||||
#[allow(missing_debug_implementations)]
|
||||
pub struct Tooltip<'a, Message, Renderer: text::Renderer> {
|
||||
content: Element<'a, Message, Renderer>,
|
||||
tooltip: Text<Renderer>,
|
||||
position: Position,
|
||||
style_sheet: Box<dyn StyleSheet + 'a>,
|
||||
gap: u16,
|
||||
padding: u16,
|
||||
}
|
||||
|
||||
impl<'a, Message, Renderer> Tooltip<'a, Message, Renderer>
|
||||
where
|
||||
Renderer: text::Renderer,
|
||||
{
|
||||
/// The default padding of a [`Tooltip`] drawn by this renderer.
|
||||
const DEFAULT_PADDING: u16 = 5;
|
||||
|
||||
/// Creates an empty [`Tooltip`].
|
||||
///
|
||||
/// [`Tooltip`]: struct.Tooltip.html
|
||||
pub fn new(
|
||||
content: impl Into<Element<'a, Message, Renderer>>,
|
||||
tooltip: impl ToString,
|
||||
position: Position,
|
||||
) -> Self {
|
||||
Tooltip {
|
||||
content: content.into(),
|
||||
tooltip: Text::new(tooltip.to_string()),
|
||||
position,
|
||||
style_sheet: Default::default(),
|
||||
gap: 0,
|
||||
padding: Self::DEFAULT_PADDING,
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the size of the text of the [`Tooltip`].
|
||||
pub fn size(mut self, size: u16) -> Self {
|
||||
self.tooltip = self.tooltip.size(size);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the font of the [`Tooltip`].
|
||||
///
|
||||
/// [`Font`]: Renderer::Font
|
||||
pub fn font(mut self, font: impl Into<Renderer::Font>) -> Self {
|
||||
self.tooltip = self.tooltip.font(font);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the gap between the content and its [`Tooltip`].
|
||||
pub fn gap(mut self, gap: u16) -> Self {
|
||||
self.gap = gap;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the padding of the [`Tooltip`].
|
||||
pub fn padding(mut self, padding: u16) -> Self {
|
||||
self.padding = padding;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the style of the [`Tooltip`].
|
||||
pub fn style(
|
||||
mut self,
|
||||
style_sheet: impl Into<Box<dyn StyleSheet + 'a>>,
|
||||
) -> Self {
|
||||
self.style_sheet = style_sheet.into();
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Message, Renderer> Widget<Message, Renderer>
|
||||
for Tooltip<'a, Message, Renderer>
|
||||
where
|
||||
Renderer: text::Renderer,
|
||||
{
|
||||
fn children(&self) -> Vec<Tree> {
|
||||
vec![Tree::new(&self.content)]
|
||||
}
|
||||
|
||||
fn diff(&self, tree: &mut Tree) {
|
||||
tree.diff_children(std::slice::from_ref(&self.content))
|
||||
}
|
||||
|
||||
fn width(&self) -> Length {
|
||||
self.content.as_widget().width()
|
||||
}
|
||||
|
||||
fn height(&self) -> Length {
|
||||
self.content.as_widget().height()
|
||||
}
|
||||
|
||||
fn layout(
|
||||
&self,
|
||||
renderer: &Renderer,
|
||||
limits: &layout::Limits,
|
||||
) -> layout::Node {
|
||||
self.content.as_widget().layout(renderer, limits)
|
||||
}
|
||||
|
||||
fn on_event(
|
||||
&mut self,
|
||||
tree: &mut Tree,
|
||||
event: Event,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
renderer: &Renderer,
|
||||
clipboard: &mut dyn Clipboard,
|
||||
shell: &mut Shell<'_, Message>,
|
||||
) -> event::Status {
|
||||
self.content.as_widget_mut().on_event(
|
||||
&mut tree.children[0],
|
||||
event,
|
||||
layout,
|
||||
cursor_position,
|
||||
renderer,
|
||||
clipboard,
|
||||
shell,
|
||||
)
|
||||
}
|
||||
|
||||
fn mouse_interaction(
|
||||
&self,
|
||||
tree: &Tree,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
viewport: &Rectangle,
|
||||
renderer: &Renderer,
|
||||
) -> mouse::Interaction {
|
||||
self.content.as_widget().mouse_interaction(
|
||||
&tree.children[0],
|
||||
layout.children().next().unwrap(),
|
||||
cursor_position,
|
||||
viewport,
|
||||
renderer,
|
||||
)
|
||||
}
|
||||
|
||||
fn draw(
|
||||
&self,
|
||||
tree: &Tree,
|
||||
renderer: &mut Renderer,
|
||||
inherited_style: &renderer::Style,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
viewport: &Rectangle,
|
||||
) {
|
||||
self.content.as_widget().draw(
|
||||
&tree.children[0],
|
||||
renderer,
|
||||
inherited_style,
|
||||
layout,
|
||||
cursor_position,
|
||||
viewport,
|
||||
);
|
||||
|
||||
let tooltip = &self.tooltip;
|
||||
|
||||
tooltip::draw(
|
||||
renderer,
|
||||
inherited_style,
|
||||
layout,
|
||||
cursor_position,
|
||||
viewport,
|
||||
self.position,
|
||||
self.gap,
|
||||
self.padding,
|
||||
self.style_sheet.as_ref(),
|
||||
|renderer, limits| {
|
||||
Widget::<(), Renderer>::layout(tooltip, renderer, limits)
|
||||
},
|
||||
|renderer, defaults, layout, cursor_position, viewport| {
|
||||
Widget::<(), Renderer>::draw(
|
||||
tooltip,
|
||||
&Tree::empty(),
|
||||
renderer,
|
||||
defaults,
|
||||
layout,
|
||||
cursor_position,
|
||||
viewport,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
fn overlay<'b>(
|
||||
&'b self,
|
||||
tree: &'b mut Tree,
|
||||
layout: Layout<'_>,
|
||||
renderer: &Renderer,
|
||||
) -> Option<overlay::Element<'b, Message, Renderer>> {
|
||||
self.content.as_widget().overlay(
|
||||
&mut tree.children[0],
|
||||
layout,
|
||||
renderer,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Message, Renderer> From<Tooltip<'a, Message, Renderer>>
|
||||
for Element<'a, Message, Renderer>
|
||||
where
|
||||
Renderer: 'a + text::Renderer,
|
||||
Message: 'a,
|
||||
{
|
||||
fn from(
|
||||
tooltip: Tooltip<'a, Message, Renderer>,
|
||||
) -> Element<'a, Message, Renderer> {
|
||||
Element::new(tooltip)
|
||||
}
|
||||
}
|
||||
|
|
@ -118,6 +118,15 @@ pub mod text_input {
|
|||
iced_pure::widget::TextInput<'a, Message, Renderer>;
|
||||
}
|
||||
|
||||
pub mod tooltip {
|
||||
//! Display a widget over another.
|
||||
pub use iced_pure::widget::tooltip::Position;
|
||||
|
||||
/// A widget allowing the selection of a single value from a list of options.
|
||||
pub type Tooltip<'a, Message> =
|
||||
iced_pure::widget::Tooltip<'a, Message, crate::Renderer>;
|
||||
}
|
||||
|
||||
pub use iced_pure::widget::progress_bar;
|
||||
pub use iced_pure::widget::rule;
|
||||
pub use iced_pure::widget::slider;
|
||||
|
|
@ -135,6 +144,7 @@ pub use scrollable::Scrollable;
|
|||
pub use slider::Slider;
|
||||
pub use text_input::TextInput;
|
||||
pub use toggler::Toggler;
|
||||
pub use tooltip::Tooltip;
|
||||
|
||||
#[cfg(feature = "canvas")]
|
||||
pub use iced_graphics::widget::pure::canvas;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue