Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

pin widget #2673

Merged
merged 2 commits into from
Nov 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 23 additions & 1 deletion examples/layout/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ use iced::keyboard;
use iced::mouse;
use iced::widget::{
button, canvas, center, checkbox, column, container, horizontal_rule,
horizontal_space, pick_list, row, scrollable, text, vertical_rule,
horizontal_space, pick_list, pin, row, scrollable, stack, text,
vertical_rule,
};
use iced::{
color, Center, Element, Fill, Font, Length, Point, Rectangle, Renderer,
Expand Down Expand Up @@ -151,6 +152,10 @@ impl Example {
title: "Quotes",
view: quotes,
},
Self {
title: "Pinning",
view: pinning,
},
];

fn is_first(self) -> bool {
Expand Down Expand Up @@ -309,6 +314,23 @@ fn quotes<'a>() -> Element<'a, Message> {
.into()
}

fn pinning<'a>() -> Element<'a, Message> {
column![
"The pin widget can be used to position a widget \
at some fixed coordinates inside some other widget.",
stack![
container(pin("• (50, 50)").x(50).y(50))
.width(500)
.height(500)
.style(container::bordered_box),
pin("• (300, 300)").x(300).y(300),
]
]
.align_x(Center)
.spacing(10)
.into()
}

fn square<'a>(size: impl Into<Length> + Copy) -> Element<'a, Message> {
struct Square;

Expand Down
34 changes: 33 additions & 1 deletion widget/src/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ use crate::text_input::{self, TextInput};
use crate::toggler::{self, Toggler};
use crate::tooltip::{self, Tooltip};
use crate::vertical_slider::{self, VerticalSlider};
use crate::{Column, MouseArea, Row, Space, Stack, Themer};
use crate::{Column, MouseArea, Pin, Row, Space, Stack, Themer};

use std::borrow::Borrow;
use std::ops::RangeInclusive;
Expand Down Expand Up @@ -249,6 +249,38 @@ where
container(content).center(Length::Fill)
}

/// Creates a new [`Pin`] widget with the given content.
///
/// A [`Pin`] widget positions its contents at some fixed coordinates inside of its boundaries.
hecrj marked this conversation as resolved.
Show resolved Hide resolved
///
/// # Example
/// ```no_run
/// # mod iced { pub mod widget { pub use iced_widget::*; } pub use iced_widget::core::Length::Fill; }
/// # pub type State = ();
/// # pub type Element<'a, Message> = iced_widget::core::Element<'a, Message, iced_widget::Theme, iced_widget::Renderer>;
/// use iced::widget::pin;
/// use iced::Fill;
///
/// enum Message {
/// // ...
/// }
///
/// fn view(state: &State) -> Element<'_, Message> {
/// pin("This text is displayed at coordinates (50, 50)!")
/// .x(50)
/// .y(50)
/// .into()
/// }
/// ```
pub fn pin<'a, Message, Theme, Renderer>(
content: impl Into<Element<'a, Message, Theme, Renderer>>,
) -> Pin<'a, Message, Theme, Renderer>
where
Renderer: core::Renderer,
{
Pin::new(content)
}

/// Creates a new [`Column`] with the given children.
///
/// Columns distribute their children vertically.
Expand Down
3 changes: 3 additions & 0 deletions widget/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ pub use iced_runtime::core;
mod action;
mod column;
mod mouse_area;
mod pin;
mod row;
mod space;
mod stack;
Expand Down Expand Up @@ -63,6 +64,8 @@ pub use pane_grid::PaneGrid;
#[doc(no_inline)]
pub use pick_list::PickList;
#[doc(no_inline)]
pub use pin::Pin;
#[doc(no_inline)]
pub use progress_bar::ProgressBar;
#[doc(no_inline)]
pub use radio::Radio;
Expand Down
270 changes: 270 additions & 0 deletions widget/src/pin.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,270 @@
//! A pin widget positions a widget at some fixed coordinates inside its boundaries.
//!
//! # Example
//! ```no_run
//! # mod iced { pub mod widget { pub use iced_widget::*; } pub use iced_widget::core::Length::Fill; }
//! # pub type State = ();
//! # pub type Element<'a, Message> = iced_widget::core::Element<'a, Message, iced_widget::Theme, iced_widget::Renderer>;
//! use iced::widget::pin;
//! use iced::Fill;
//!
//! enum Message {
//! // ...
//! }
//!
//! fn view(state: &State) -> Element<'_, Message> {
//! pin("This text is displayed at coordinates (50, 50)!")
//! .x(50)
//! .y(50)
//! .into()
//! }
//! ```
use crate::core::layout;
use crate::core::mouse;
use crate::core::overlay;
use crate::core::renderer;
use crate::core::widget;
use crate::core::{
self, Clipboard, Element, Event, Layout, Length, Pixels, Point, Rectangle,
Shell, Size, Vector, Widget,
};

/// A widget that positions its contents at some fixed coordinates inside of its boundaries.
///
/// By default, a [`Pin`] widget will try to fill its parent.
///
/// # Example
/// ```no_run
/// # mod iced { pub mod widget { pub use iced_widget::*; } pub use iced_widget::core::Length::Fill; }
/// # pub type State = ();
/// # pub type Element<'a, Message> = iced_widget::core::Element<'a, Message, iced_widget::Theme, iced_widget::Renderer>;
/// use iced::widget::pin;
/// use iced::Fill;
///
/// enum Message {
/// // ...
/// }
///
/// fn view(state: &State) -> Element<'_, Message> {
/// pin("This text is displayed at coordinates (50, 50)!")
/// .x(50)
/// .y(50)
/// .into()
/// }
/// ```
#[allow(missing_debug_implementations)]
pub struct Pin<'a, Message, Theme = crate::Theme, Renderer = crate::Renderer>
where
Renderer: core::Renderer,
{
content: Element<'a, Message, Theme, Renderer>,
width: Length,
height: Length,
position: Point,
}

impl<'a, Message, Theme, Renderer> Pin<'a, Message, Theme, Renderer>
where
Renderer: core::Renderer,
{
/// Creates a [`Pin`] widget with the given content.
pub fn new(
content: impl Into<Element<'a, Message, Theme, Renderer>>,
) -> Self {
Self {
content: content.into(),
width: Length::Fill,
height: Length::Fill,
position: Point::ORIGIN,
}
}

/// Sets the width of the [`Pin`].
pub fn width(mut self, width: impl Into<Length>) -> Self {
self.width = width.into();
self
}

/// Sets the height of the [`Pin`].
pub fn height(mut self, height: impl Into<Length>) -> Self {
self.height = height.into();
self
}

/// Sets the position of the [`Pin`]; where the pinned widget will be displayed.
pub fn position(mut self, position: impl Into<Point>) -> Self {
self.position = position.into();
self
}

/// Sets the X coordinate of the [`Pin`].
pub fn x(mut self, x: impl Into<Pixels>) -> Self {
self.position.x = x.into().0;
self
}

/// Sets the Y coordinate of the [`Pin`].
pub fn y(mut self, y: impl Into<Pixels>) -> Self {
self.position.y = y.into().0;
self
}
}

impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
for Pin<'a, Message, Theme, Renderer>
where
Renderer: core::Renderer,
{
fn tag(&self) -> widget::tree::Tag {
self.content.as_widget().tag()
}

fn state(&self) -> widget::tree::State {
self.content.as_widget().state()
}

fn children(&self) -> Vec<widget::Tree> {
self.content.as_widget().children()
}

fn diff(&self, tree: &mut widget::Tree) {
self.content.as_widget().diff(tree);
}

fn size(&self) -> Size<Length> {
Size {
width: self.width,
height: self.height,
}
}

fn layout(
&self,
tree: &mut widget::Tree,
renderer: &Renderer,
limits: &layout::Limits,
) -> layout::Node {
let limits = limits.width(self.width).height(self.height);

let available =
limits.max() - Size::new(self.position.x, self.position.y);

let node = self
.content
.as_widget()
.layout(tree, renderer, &layout::Limits::new(Size::ZERO, available))
.move_to(self.position);

let size = limits.resolve(self.width, self.height, node.size());
layout::Node::with_children(size, vec![node])
}

fn operate(
&self,
tree: &mut widget::Tree,
layout: Layout<'_>,
renderer: &Renderer,
operation: &mut dyn widget::Operation,
) {
self.content.as_widget().operate(
tree,
layout.children().next().unwrap(),
renderer,
operation,
);
}

fn update(
&mut self,
tree: &mut widget::Tree,
event: Event,
layout: Layout<'_>,
cursor: mouse::Cursor,
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
viewport: &Rectangle,
) {
self.content.as_widget_mut().update(
tree,
event,
layout.children().next().unwrap(),
cursor,
renderer,
clipboard,
shell,
viewport,
);
}

fn mouse_interaction(
&self,
tree: &widget::Tree,
layout: Layout<'_>,
cursor: mouse::Cursor,
viewport: &Rectangle,
renderer: &Renderer,
) -> mouse::Interaction {
self.content.as_widget().mouse_interaction(
tree,
layout.children().next().unwrap(),
cursor,
viewport,
renderer,
)
}

fn draw(
&self,
tree: &widget::Tree,
renderer: &mut Renderer,
theme: &Theme,
style: &renderer::Style,
layout: Layout<'_>,
cursor: mouse::Cursor,
viewport: &Rectangle,
) {
let bounds = layout.bounds();

if let Some(clipped_viewport) = bounds.intersection(viewport) {
self.content.as_widget().draw(
tree,
renderer,
theme,
style,
layout.children().next().unwrap(),
cursor,
&clipped_viewport,
);
}
}

fn overlay<'b>(
&'b mut self,
tree: &'b mut widget::Tree,
layout: Layout<'_>,
renderer: &Renderer,
translation: Vector,
) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
self.content.as_widget_mut().overlay(
tree,
layout.children().next().unwrap(),
renderer,
translation,
)
}
}

impl<'a, Message, Theme, Renderer> From<Pin<'a, Message, Theme, Renderer>>
for Element<'a, Message, Theme, Renderer>
where
Message: 'a,
Theme: 'a,
Renderer: core::Renderer + 'a,
{
fn from(
pin: Pin<'a, Message, Theme, Renderer>,
) -> Element<'a, Message, Theme, Renderer> {
Element::new(pin)
}
}
Loading