Skip to content

Commit

Permalink
Merge pull request #1193 from iced-rs/responsive-widget
Browse files Browse the repository at this point in the history
`Responsive` widget
  • Loading branch information
hecrj authored Jan 13, 2022
2 parents 1a31aef + f6c436a commit 15a13a7
Show file tree
Hide file tree
Showing 34 changed files with 765 additions and 131 deletions.
1 change: 1 addition & 0 deletions examples/pane_grid/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ publish = false
[dependencies]
iced = { path = "../..", features = ["debug"] }
iced_native = { path = "../../native" }
iced_lazy = { path = "../../lazy" }
47 changes: 31 additions & 16 deletions examples/pane_grid/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
use iced::alignment::{self, Alignment};
use iced::button::{self, Button};
use iced::executor;
use iced::keyboard;
use iced::pane_grid::{self, PaneGrid};
use iced::scrollable::{self, Scrollable};
use iced::{
alignment, button, executor, keyboard, pane_grid, scrollable, Alignment,
Application, Button, Color, Column, Command, Container, Element, Length,
PaneGrid, Row, Scrollable, Settings, Subscription, Text,
Application, Color, Column, Command, Container, Element, Length, Row,
Settings, Size, Subscription, Text,
};
use iced_lazy::responsive::{self, Responsive};
use iced_native::{event, subscription, Event};

pub fn main() -> iced::Result {
Expand Down Expand Up @@ -154,17 +160,24 @@ impl Application for Example {
let pane_grid = PaneGrid::new(&mut self.panes, |id, pane| {
let is_focused = focus == Some(id);

let text = if pane.is_pinned { "Unpin" } else { "Pin" };
let pin_button =
Button::new(&mut pane.pin_button, Text::new(text).size(14))
.on_press(Message::TogglePin(id))
.style(style::Button::Pin)
.padding(3);
let Pane {
responsive,
pin_button,
is_pinned,
content,
..
} = pane;

let text = if *is_pinned { "Unpin" } else { "Pin" };
let pin_button = Button::new(pin_button, Text::new(text).size(14))
.on_press(Message::TogglePin(id))
.style(style::Button::Pin)
.padding(3);

let title = Row::with_children(vec![
pin_button.into(),
Text::new("Pane").into(),
Text::new(pane.content.id.to_string())
Text::new(content.id.to_string())
.color(if is_focused {
PANE_ID_COLOR_FOCUSED
} else {
Expand All @@ -175,19 +188,17 @@ impl Application for Example {
.spacing(5);

let title_bar = pane_grid::TitleBar::new(title)
.controls(pane.controls.view(id, total_panes, pane.is_pinned))
.controls(pane.controls.view(id, total_panes, *is_pinned))
.padding(10)
.style(if is_focused {
style::TitleBar::Focused
} else {
style::TitleBar::Active
});

pane_grid::Content::new(pane.content.view(
id,
total_panes,
pane.is_pinned,
))
pane_grid::Content::new(Responsive::new(responsive, move |size| {
content.view(id, total_panes, *is_pinned, size)
}))
.title_bar(title_bar)
.style(if is_focused {
style::Pane::Focused
Expand Down Expand Up @@ -242,6 +253,7 @@ fn handle_hotkey(key_code: keyboard::KeyCode) -> Option<Message> {
}

struct Pane {
pub responsive: responsive::State,
pub is_pinned: bool,
pub pin_button: button::State,
pub content: Content,
Expand All @@ -263,6 +275,7 @@ struct Controls {
impl Pane {
fn new(id: usize) -> Self {
Self {
responsive: responsive::State::new(),
is_pinned: false,
pin_button: button::State::new(),
content: Content::new(id),
Expand All @@ -286,6 +299,7 @@ impl Content {
pane: pane_grid::Pane,
total_panes: usize,
is_pinned: bool,
size: Size,
) -> Element<Message> {
let Content {
scroll,
Expand Down Expand Up @@ -338,6 +352,7 @@ impl Content {
.width(Length::Fill)
.spacing(10)
.align_items(Alignment::Center)
.push(Text::new(format!("{}x{}", size.width, size.height)).size(24))
.push(controls);

Container::new(content)
Expand Down
14 changes: 10 additions & 4 deletions glutin/src/application.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ use iced_winit::application;
use iced_winit::conversion;
use iced_winit::futures;
use iced_winit::futures::channel::mpsc;
use iced_winit::{Cache, Clipboard, Debug, Proxy, Settings};
use iced_winit::user_interface;
use iced_winit::{Clipboard, Debug, Proxy, Settings};

use glutin::window::Window;
use std::mem::ManuallyDrop;
Expand Down Expand Up @@ -177,7 +178,7 @@ async fn run_instance<A, E, C>(
let mut user_interface =
ManuallyDrop::new(application::build_user_interface(
&mut application,
Cache::default(),
user_interface::Cache::default(),
&mut renderer,
state.logical_size(),
&mut debug,
Expand All @@ -198,7 +199,7 @@ async fn run_instance<A, E, C>(

debug.event_processing_started();

let statuses = user_interface.update(
let (interface_state, statuses) = user_interface.update(
&events,
state.cursor_position(),
&mut renderer,
Expand All @@ -212,7 +213,12 @@ async fn run_instance<A, E, C>(
runtime.broadcast(event);
}

if !messages.is_empty() {
if !messages.is_empty()
|| matches!(
interface_state,
user_interface::State::Outdated
)
{
let cache =
ManuallyDrop::into_inner(user_interface).into_cache();

Expand Down
1 change: 1 addition & 0 deletions graphics/src/widget/canvas.rs
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,7 @@ where
layout: Layout<'_>,
cursor_position: Point,
_viewport: &Rectangle,
_renderer: &Renderer<B>,
) -> mouse::Interaction {
let bounds = layout.bounds();
let cursor = Cursor::from_window_position(cursor_position);
Expand Down
13 changes: 13 additions & 0 deletions lazy/src/cache.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
use iced_native::overlay;
use iced_native::Element;

use ouroboros::self_referencing;

#[self_referencing(pub_extras)]
pub struct Cache<'a, Message: 'a, Renderer: 'a> {
pub element: Element<'a, Message, Renderer>,

#[borrows(mut element)]
#[covariant]
pub overlay: Option<overlay::Element<'this, Message, Renderer>>,
}
88 changes: 58 additions & 30 deletions lazy/src/component.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
//! Build and reuse custom widgets using The Elm Architecture.
use crate::{Cache, CacheBuilder};

use iced_native::event;
use iced_native::layout::{self, Layout};
use iced_native::mouse;
Expand All @@ -12,6 +15,35 @@ use std::cell::RefCell;
use std::hash::Hash;
use std::marker::PhantomData;

/// A reusable, custom widget that uses The Elm Architecture.
///
/// A [`Component`] allows you to implement custom widgets as if they were
/// `iced` applications with encapsulated state.
///
/// In other words, a [`Component`] allows you to turn `iced` applications into
/// custom widgets and embed them without cumbersome wiring.
///
/// A [`Component`] produces widgets that may fire an [`Event`](Component::Event)
/// and update the internal state of the [`Component`].
///
/// Additionally, a [`Component`] is capable of producing a `Message` to notify
/// the parent application of any relevant interactions.
pub trait Component<Message, Renderer> {
/// The type of event this [`Component`] handles internally.
type Event;

/// Processes an [`Event`](Component::Event) and updates the [`Component`] state accordingly.
///
/// It can produce a `Message` for the parent application.
fn update(&mut self, event: Self::Event) -> Option<Message>;

/// Produces the widgets of the [`Component`], which may trigger an [`Event`](Component::Event)
/// on user interaction.
fn view(&mut self) -> Element<Self::Event, Renderer>;
}

/// Turns an implementor of [`Component`] into an [`Element`] that can be
/// embedded in any application.
pub fn view<'a, C, Message, Renderer>(
component: C,
) -> Element<'a, Message, Renderer>
Expand All @@ -24,11 +56,11 @@ where
state: RefCell::new(Some(
StateBuilder {
component: Box::new(component),
message: PhantomData,
cache_builder: |state| {
Some(
CacheBuilder {
element: state.view(),
message: PhantomData,
overlay_builder: |_| None,
}
.build(),
Expand All @@ -40,35 +72,18 @@ where
})
}

pub trait Component<Message, Renderer> {
type Event;

fn update(&mut self, event: Self::Event) -> Option<Message>;

fn view(&mut self) -> Element<Self::Event, Renderer>;
}

struct Instance<'a, Message, Renderer, Event> {
state: RefCell<Option<State<'a, Message, Renderer, Event>>>,
}

#[self_referencing]
struct State<'a, Message: 'a, Renderer: 'a, Event: 'a> {
component: Box<dyn Component<Message, Renderer, Event = Event> + 'a>,

#[borrows(mut component)]
#[covariant]
cache: Option<Cache<'this, Message, Renderer, Event>>,
}

#[self_referencing]
struct Cache<'a, Message, Renderer: 'a, Event: 'a> {
element: Element<'a, Event, Renderer>,
message: PhantomData<Message>,

#[borrows(mut element)]
#[borrows(mut component)]
#[covariant]
overlay: Option<overlay::Element<'this, Event, Renderer>>,
cache: Option<Cache<'this, Event, Renderer>>,
}

impl<'a, Message, Renderer, Event> Instance<'a, Message, Renderer, Event> {
Expand All @@ -94,7 +109,6 @@ impl<'a, Message, Renderer, Event> Instance<'a, Message, Renderer, Event> {
*cache = Some(
CacheBuilder {
element,
message: PhantomData,
overlay_builder: |_| None,
}
.build(),
Expand Down Expand Up @@ -149,7 +163,7 @@ where
)
});

local_shell.with_invalid_layout(|| shell.invalidate_layout());
local_shell.revalidate_layout(|| shell.invalidate_layout());

if !local_messages.is_empty() {
let mut component = self
Expand All @@ -170,11 +184,11 @@ where
*self.state.borrow_mut() = Some(
StateBuilder {
component,
message: PhantomData,
cache_builder: |state| {
Some(
CacheBuilder {
element: state.view(),
message: PhantomData,
overlay_builder: |_| None,
}
.build(),
Expand Down Expand Up @@ -214,15 +228,22 @@ where
layout: Layout<'_>,
cursor_position: Point,
viewport: &Rectangle,
renderer: &Renderer,
) -> mouse::Interaction {
self.with_element(|element| {
element.mouse_interaction(layout, cursor_position, viewport)
element.mouse_interaction(
layout,
cursor_position,
viewport,
renderer,
)
})
}

fn overlay(
&mut self,
layout: Layout<'_>,
renderer: &Renderer,
) -> Option<overlay::Element<'_, Message, Renderer>> {
let has_overlay = self
.state
Expand All @@ -235,8 +256,9 @@ where
*cache = Some(
CacheBuilder {
element,
message: PhantomData,
overlay_builder: |element| element.overlay(layout),
overlay_builder: |element| {
element.overlay(layout, renderer)
},
}
.build(),
);
Expand Down Expand Up @@ -331,9 +353,15 @@ where
layout: Layout<'_>,
cursor_position: Point,
viewport: &Rectangle,
renderer: &Renderer,
) -> mouse::Interaction {
self.with_overlay_maybe(|overlay| {
overlay.mouse_interaction(layout, cursor_position, viewport)
overlay.mouse_interaction(
layout,
cursor_position,
viewport,
renderer,
)
})
.unwrap_or_default()
}
Expand Down Expand Up @@ -375,7 +403,7 @@ where
})
.unwrap_or_else(|| iced_native::event::Status::Ignored);

local_shell.with_invalid_layout(|| shell.invalidate_layout());
local_shell.revalidate_layout(|| shell.invalidate_layout());

if !local_messages.is_empty() {
let mut component =
Expand All @@ -391,13 +419,13 @@ where
self.instance.state = RefCell::new(Some(
StateBuilder {
component,
message: PhantomData,
cache_builder: |state| {
Some(
CacheBuilder {
element: state.view(),
message: PhantomData,
overlay_builder: |element| {
element.overlay(layout)
element.overlay(layout, renderer)
},
}
.build(),
Expand Down
6 changes: 6 additions & 0 deletions lazy/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
pub mod component;
pub mod responsive;

pub use component::Component;
pub use responsive::Responsive;

mod cache;

use cache::{Cache, CacheBuilder};
Loading

0 comments on commit 15a13a7

Please sign in to comment.