summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLibravatar Héctor Ramón <hector0193@gmail.com>2022-11-08 18:12:06 +0100
committerLibravatar GitHub <noreply@github.com>2022-11-08 18:12:06 +0100
commitd9f408d1c2567698c97890b1bbcc4363bcd313aa (patch)
treececd0510a60de87e59977284ddf9fc0140aedea1
parentac737cb6e905f7a8a85e9f26a27deb7907e80114 (diff)
parent7de9d2475dbf4ed93c4248580514901f82a0fc0e (diff)
downloadiced-d9f408d1c2567698c97890b1bbcc4363bcd313aa.tar.gz
iced-d9f408d1c2567698c97890b1bbcc4363bcd313aa.tar.bz2
iced-d9f408d1c2567698c97890b1bbcc4363bcd313aa.zip
Merge pull request #1504 from tarkah/feat/pane-grid-maximize
Add pane maximize / restore for `PaneGrid`
-rw-r--r--examples/pane_grid/src/main.rs40
-rw-r--r--native/src/widget/pane_grid.rs193
-rw-r--r--native/src/widget/pane_grid/state.rs66
3 files changed, 213 insertions, 86 deletions
diff --git a/examples/pane_grid/src/main.rs b/examples/pane_grid/src/main.rs
index ae8fa22b..c9f1376c 100644
--- a/examples/pane_grid/src/main.rs
+++ b/examples/pane_grid/src/main.rs
@@ -29,6 +29,8 @@ enum Message {
Dragged(pane_grid::DragEvent),
Resized(pane_grid::ResizeEvent),
TogglePin(pane_grid::Pane),
+ Maximize(pane_grid::Pane),
+ Restore,
Close(pane_grid::Pane),
CloseFocused,
}
@@ -114,6 +116,10 @@ impl Application for Example {
*is_pinned = !*is_pinned;
}
}
+ Message::Maximize(pane) => self.panes.maximize(&pane),
+ Message::Restore => {
+ self.panes.restore();
+ }
Message::Close(pane) => {
if let Some((_, sibling)) = self.panes.close(&pane) {
self.focus = Some(sibling);
@@ -157,7 +163,7 @@ impl Application for Example {
let focus = self.focus;
let total_panes = self.panes.len();
- let pane_grid = PaneGrid::new(&self.panes, |id, pane| {
+ let pane_grid = PaneGrid::new(&self.panes, |id, pane, is_maximized| {
let is_focused = focus == Some(id);
let pin_button = button(
@@ -178,7 +184,12 @@ impl Application for Example {
.spacing(5);
let title_bar = pane_grid::TitleBar::new(title)
- .controls(view_controls(id, total_panes, pane.is_pinned))
+ .controls(view_controls(
+ id,
+ total_panes,
+ pane.is_pinned,
+ is_maximized,
+ ))
.padding(10)
.style(if is_focused {
style::title_bar_focused
@@ -314,16 +325,35 @@ fn view_controls<'a>(
pane: pane_grid::Pane,
total_panes: usize,
is_pinned: bool,
+ is_maximized: bool,
) -> Element<'a, Message> {
- let mut button = button(text("Close").size(14))
+ let mut row = row![].spacing(5);
+
+ if total_panes > 1 {
+ let toggle = {
+ let (content, message) = if is_maximized {
+ ("Restore", Message::Restore)
+ } else {
+ ("Maximize", Message::Maximize(pane))
+ };
+ button(text(content).size(14))
+ .style(theme::Button::Secondary)
+ .padding(3)
+ .on_press(message)
+ };
+
+ row = row.push(toggle);
+ }
+
+ let mut close = button(text("Close").size(14))
.style(theme::Button::Destructive)
.padding(3);
if total_panes > 1 && !is_pinned {
- button = button.on_press(Message::Close(pane));
+ close = close.on_press(Message::Close(pane));
}
- button.into()
+ row.push(close).into()
}
mod style {
diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs
index 96cf78ef..fd771f8b 100644
--- a/native/src/widget/pane_grid.rs
+++ b/native/src/widget/pane_grid.rs
@@ -85,7 +85,7 @@ use crate::{
/// let (mut state, _) = pane_grid::State::new(PaneState::SomePane);
///
/// let pane_grid =
-/// PaneGrid::new(&state, |pane, state| {
+/// PaneGrid::new(&state, |pane, state, is_maximized| {
/// pane_grid::Content::new(match state {
/// PaneState::SomePane => text("This is some pane"),
/// PaneState::AnotherKindOfPane => text("This is another kind of pane"),
@@ -100,8 +100,7 @@ where
Renderer: crate::Renderer,
Renderer::Theme: StyleSheet + container::StyleSheet,
{
- state: &'a state::Internal,
- elements: Vec<(Pane, Content<'a, Message, Renderer>)>,
+ contents: Contents<'a, Content<'a, Message, Renderer>>,
width: Length,
height: Length,
spacing: u16,
@@ -119,22 +118,35 @@ where
/// Creates a [`PaneGrid`] with the given [`State`] and view function.
///
/// The view function will be called to display each [`Pane`] present in the
- /// [`State`].
+ /// [`State`]. [`bool`] is set if the pane is maximized.
pub fn new<T>(
state: &'a State<T>,
- view: impl Fn(Pane, &'a T) -> Content<'a, Message, Renderer>,
+ view: impl Fn(Pane, &'a T, bool) -> Content<'a, Message, Renderer>,
) -> Self {
- let elements = {
- state
- .panes
- .iter()
- .map(|(pane, pane_state)| (*pane, view(*pane, pane_state)))
- .collect()
+ let contents = if let Some((pane, pane_state)) =
+ state.maximized.and_then(|pane| {
+ state.panes.get(&pane).map(|pane_state| (pane, pane_state))
+ }) {
+ Contents::Maximized(
+ pane,
+ view(pane, pane_state, true),
+ Node::Pane(pane),
+ )
+ } else {
+ Contents::All(
+ state
+ .panes
+ .iter()
+ .map(|(pane, pane_state)| {
+ (*pane, view(*pane, pane_state, false))
+ })
+ .collect(),
+ &state.internal,
+ )
};
Self {
- elements,
- state: &state.internal,
+ contents,
width: Length::Fill,
height: Length::Fill,
spacing: 0,
@@ -208,6 +220,12 @@ where
self.style = style.into();
self
}
+
+ fn drag_enabled(&self) -> bool {
+ (!self.contents.is_maximized())
+ .then(|| self.on_drag.is_some())
+ .unwrap_or_default()
+ }
}
impl<'a, Message, Renderer> Widget<Message, Renderer>
@@ -225,18 +243,25 @@ where
}
fn children(&self) -> Vec<Tree> {
- self.elements
+ self.contents
.iter()
.map(|(_, content)| content.state())
.collect()
}
fn diff(&self, tree: &mut Tree) {
- tree.diff_children_custom(
- &self.elements,
- |state, (_, content)| content.diff(state),
- |(_, content)| content.state(),
- )
+ match &self.contents {
+ Contents::All(contents, _) => tree.diff_children_custom(
+ contents,
+ |state, (_, content)| content.diff(state),
+ |(_, content)| content.state(),
+ ),
+ Contents::Maximized(_, content, _) => tree.diff_children_custom(
+ &[content],
+ |state, content| content.diff(state),
+ |content| content.state(),
+ ),
+ }
}
fn width(&self) -> Length {
@@ -255,12 +280,12 @@ where
layout(
renderer,
limits,
- self.state,
+ self.contents.layout(),
self.width,
self.height,
self.spacing,
- self.elements.iter().map(|(pane, content)| (*pane, content)),
- |element, renderer, limits| element.layout(renderer, limits),
+ self.contents.iter(),
+ |content, renderer, limits| content.layout(renderer, limits),
)
}
@@ -276,28 +301,34 @@ where
) -> event::Status {
let action = tree.state.downcast_mut::<state::Action>();
+ let on_drag = if self.drag_enabled() {
+ &self.on_drag
+ } else {
+ &None
+ };
+
let event_status = update(
action,
- self.state,
+ self.contents.layout(),
&event,
layout,
cursor_position,
shell,
self.spacing,
- self.elements.iter().map(|(pane, content)| (*pane, content)),
+ self.contents.iter(),
&self.on_click,
- &self.on_drag,
+ on_drag,
&self.on_resize,
);
let picked_pane = action.picked_pane().map(|(pane, _)| pane);
- self.elements
+ self.contents
.iter_mut()
.zip(&mut tree.children)
.zip(layout.children())
.map(|(((pane, content), tree), layout)| {
- let is_picked = picked_pane == Some(*pane);
+ let is_picked = picked_pane == Some(pane);
content.on_event(
tree,
@@ -323,14 +354,14 @@ where
) -> mouse::Interaction {
mouse_interaction(
tree.state.downcast_ref(),
- self.state,
+ self.contents.layout(),
layout,
cursor_position,
self.spacing,
self.on_resize.as_ref().map(|(leeway, _)| *leeway),
)
.unwrap_or_else(|| {
- self.elements
+ self.contents
.iter()
.zip(&tree.children)
.zip(layout.children())
@@ -341,7 +372,7 @@ where
cursor_position,
viewport,
renderer,
- self.on_drag.is_some(),
+ self.drag_enabled(),
)
})
.max()
@@ -361,7 +392,7 @@ where
) {
draw(
tree.state.downcast_ref(),
- self.state,
+ self.contents.layout(),
layout,
cursor_position,
renderer,
@@ -371,10 +402,10 @@ where
self.spacing,
self.on_resize.as_ref().map(|(leeway, _)| *leeway),
self.style,
- self.elements
+ self.contents
.iter()
.zip(&tree.children)
- .map(|((pane, content), tree)| (*pane, (content, tree))),
+ .map(|((pane, content), tree)| (pane, (content, tree))),
|(content, tree),
renderer,
style,
@@ -400,7 +431,7 @@ where
layout: Layout<'_>,
renderer: &Renderer,
) -> Option<overlay::Element<'_, Message, Renderer>> {
- self.elements
+ self.contents
.iter()
.zip(&mut tree.children)
.zip(layout.children())
@@ -429,24 +460,24 @@ where
pub fn layout<Renderer, T>(
renderer: &Renderer,
limits: &layout::Limits,
- state: &state::Internal,
+ node: &Node,
width: Length,
height: Length,
spacing: u16,
- elements: impl Iterator<Item = (Pane, T)>,
- layout_element: impl Fn(T, &Renderer, &layout::Limits) -> layout::Node,
+ contents: impl Iterator<Item = (Pane, T)>,
+ layout_content: impl Fn(T, &Renderer, &layout::Limits) -> layout::Node,
) -> layout::Node {
let limits = limits.width(width).height(height);
let size = limits.resolve(Size::ZERO);
- let regions = state.pane_regions(f32::from(spacing), size);
- let children = elements
- .filter_map(|(pane, element)| {
+ let regions = node.pane_regions(f32::from(spacing), size);
+ let children = contents
+ .filter_map(|(pane, content)| {
let region = regions.get(&pane)?;
let size = Size::new(region.width, region.height);
- let mut node = layout_element(
- element,
+ let mut node = layout_content(
+ content,
renderer,
&layout::Limits::new(size, size),
);
@@ -464,13 +495,13 @@ pub fn layout<Renderer, T>(
/// accordingly.
pub fn update<'a, Message, T: Draggable>(
action: &mut state::Action,
- state: &state::Internal,
+ node: &Node,
event: &Event,
layout: Layout<'_>,
cursor_position: Point,
shell: &mut Shell<'_, Message>,
spacing: u16,
- elements: impl Iterator<Item = (Pane, T)>,
+ contents: impl Iterator<Item = (Pane, T)>,
on_click: &Option<Box<dyn Fn(Pane) -> Message + 'a>>,
on_drag: &Option<Box<dyn Fn(DragEvent) -> Message + 'a>>,
on_resize: &Option<(u16, Box<dyn Fn(ResizeEvent) -> Message + 'a>)>,
@@ -492,7 +523,7 @@ pub fn update<'a, Message, T: Draggable>(
cursor_position.y - bounds.y,
);
- let splits = state.split_regions(
+ let splits = node.split_regions(
f32::from(spacing),
Size::new(bounds.width, bounds.height),
);
@@ -514,7 +545,7 @@ pub fn update<'a, Message, T: Draggable>(
layout,
cursor_position,
shell,
- elements,
+ contents,
on_click,
on_drag,
);
@@ -526,7 +557,7 @@ pub fn update<'a, Message, T: Draggable>(
layout,
cursor_position,
shell,
- elements,
+ contents,
on_click,
on_drag,
);
@@ -539,7 +570,7 @@ pub fn update<'a, Message, T: Draggable>(
| Event::Touch(touch::Event::FingerLost { .. }) => {
if let Some((pane, _)) = action.picked_pane() {
if let Some(on_drag) = on_drag {
- let mut dropped_region = elements
+ let mut dropped_region = contents
.zip(layout.children())
.filter(|(_, layout)| {
layout.bounds().contains(cursor_position)
@@ -570,7 +601,7 @@ pub fn update<'a, Message, T: Draggable>(
if let Some((split, _)) = action.picked_split() {
let bounds = layout.bounds();
- let splits = state.split_regions(
+ let splits = node.split_regions(
f32::from(spacing),
Size::new(bounds.width, bounds.height),
);
@@ -609,13 +640,13 @@ fn click_pane<'a, Message, T>(
layout: Layout<'_>,
cursor_position: Point,
shell: &mut Shell<'_, Message>,
- elements: impl Iterator<Item = (Pane, T)>,
+ contents: impl Iterator<Item = (Pane, T)>,
on_click: &Option<Box<dyn Fn(Pane) -> Message + 'a>>,
on_drag: &Option<Box<dyn Fn(DragEvent) -> Message + 'a>>,
) where
T: Draggable,
{
- let mut clicked_region = elements
+ let mut clicked_region = contents
.zip(layout.children())
.filter(|(_, layout)| layout.bounds().contains(cursor_position));
@@ -642,7 +673,7 @@ fn click_pane<'a, Message, T>(
/// Returns the current [`mouse::Interaction`] of a [`PaneGrid`].
pub fn mouse_interaction(
action: &state::Action,
- state: &state::Internal,
+ node: &Node,
layout: Layout<'_>,
cursor_position: Point,
spacing: u16,
@@ -658,7 +689,7 @@ pub fn mouse_interaction(
let bounds = layout.bounds();
let splits =
- state.split_regions(f32::from(spacing), bounds.size());
+ node.split_regions(f32::from(spacing), bounds.size());
let relative_cursor = Point::new(
cursor_position.x - bounds.x,
@@ -687,7 +718,7 @@ pub fn mouse_interaction(
/// Draws a [`PaneGrid`].
pub fn draw<Renderer, T>(
action: &state::Action,
- state: &state::Internal,
+ node: &Node,
layout: Layout<'_>,
cursor_position: Point,
renderer: &mut Renderer,
@@ -697,7 +728,7 @@ pub fn draw<Renderer, T>(
spacing: u16,
resize_leeway: Option<u16>,
style: <Renderer::Theme as StyleSheet>::Style,
- elements: impl Iterator<Item = (Pane, T)>,
+ contents: impl Iterator<Item = (Pane, T)>,
draw_pane: impl Fn(
T,
&mut Renderer,
@@ -717,7 +748,7 @@ pub fn draw<Renderer, T>(
.and_then(|(split, axis)| {
let bounds = layout.bounds();
- let splits = state.split_regions(f32::from(spacing), bounds.size());
+ let splits = node.split_regions(f32::from(spacing), bounds.size());
let (_axis, region, ratio) = splits.get(&split)?;
@@ -736,7 +767,7 @@ pub fn draw<Renderer, T>(
);
let splits =
- state.split_regions(f32::from(spacing), bounds.size());
+ node.split_regions(f32::from(spacing), bounds.size());
let (_split, axis, region) = hovered_split(
splits.iter(),
@@ -759,7 +790,7 @@ pub fn draw<Renderer, T>(
let mut render_picked_pane = None;
- for ((id, pane), layout) in elements.zip(layout.children()) {
+ for ((id, pane), layout) in contents.zip(layout.children()) {
match picked_pane {
Some((dragging, origin)) if id == dragging => {
render_picked_pane = Some((pane, origin, layout));
@@ -897,3 +928,49 @@ fn hovered_split<'a>(
})
.next()
}
+
+/// The visible contents of the [`PaneGrid`]
+#[derive(Debug)]
+pub enum Contents<'a, T> {
+ /// All panes are visible
+ All(Vec<(Pane, T)>, &'a state::Internal),
+ /// A maximized pane is visible
+ Maximized(Pane, T, Node),
+}
+
+impl<'a, T> Contents<'a, T> {
+ /// Returns the layout [`Node`] of the [`Contents`]
+ pub fn layout(&self) -> &Node {
+ match self {
+ Contents::All(_, state) => state.layout(),
+ Contents::Maximized(_, _, layout) => layout,
+ }
+ }
+
+ /// Returns an iterator over the values of the [`Contents`]
+ pub fn iter(&self) -> Box<dyn Iterator<Item = (Pane, &T)> + '_> {
+ match self {
+ Contents::All(contents, _) => Box::new(
+ contents.iter().map(|(pane, content)| (*pane, content)),
+ ),
+ Contents::Maximized(pane, content, _) => {
+ Box::new(std::iter::once((*pane, content)))
+ }
+ }
+ }
+
+ fn iter_mut(&mut self) -> Box<dyn Iterator<Item = (Pane, &mut T)> + '_> {
+ match self {
+ Contents::All(contents, _) => Box::new(
+ contents.iter_mut().map(|(pane, content)| (*pane, content)),
+ ),
+ Contents::Maximized(pane, content, _) => {
+ Box::new(std::iter::once((*pane, content)))
+ }
+ }
+ }
+
+ fn is_maximized(&self) -> bool {
+ matches!(self, Self::Maximized(..))
+ }
+}
diff --git a/native/src/widget/pane_grid/state.rs b/native/src/widget/pane_grid/state.rs
index cdca6267..58397444 100644
--- a/native/src/widget/pane_grid/state.rs
+++ b/native/src/widget/pane_grid/state.rs
@@ -4,9 +4,9 @@
use crate::widget::pane_grid::{
Axis, Configuration, Direction, Node, Pane, Split,
};
-use crate::{Point, Rectangle, Size};
+use crate::{Point, Size};
-use std::collections::{BTreeMap, HashMap};
+use std::collections::HashMap;
/// The state of a [`PaneGrid`].
///
@@ -31,6 +31,9 @@ pub struct State<T> {
///
/// [`PaneGrid`]: crate::widget::PaneGrid
pub internal: Internal,
+
+ /// The maximized [`Pane`] of the [`PaneGrid`]
+ pub(super) maximized: Option<Pane>,
}
impl<T> State<T> {
@@ -52,7 +55,11 @@ impl<T> State<T> {
let internal =
Internal::from_configuration(&mut panes, config.into(), 0);
- State { panes, internal }
+ State {
+ panes,
+ internal,
+ maximized: None,
+ }
}
/// Returns the total amount of panes in the [`State`].
@@ -153,6 +160,7 @@ impl<T> State<T> {
node.split(new_split, axis, new_pane);
let _ = self.panes.insert(new_pane, state);
+ let _ = self.maximized.take();
Some((new_pane, new_split))
}
@@ -194,12 +202,39 @@ impl<T> State<T> {
/// Closes the given [`Pane`] and returns its internal state and its closest
/// sibling, if it exists.
pub fn close(&mut self, pane: &Pane) -> Option<(T, Pane)> {
+ if self.maximized == Some(*pane) {
+ let _ = self.maximized.take();
+ }
+
if let Some(sibling) = self.internal.layout.remove(pane) {
self.panes.remove(pane).map(|state| (state, sibling))
} else {
None
}
}
+
+ /// Maximize the given [`Pane`]. Only this pane will be rendered by the
+ /// [`PaneGrid`] until [`Self::restore()`] is called.
+ ///
+ /// [`PaneGrid`]: crate::widget::PaneGrid
+ pub fn maximize(&mut self, pane: &Pane) {
+ self.maximized = Some(*pane);
+ }
+
+ /// Restore the currently maximized [`Pane`] to it's normal size. All panes
+ /// will be rendered by the [`PaneGrid`].
+ ///
+ /// [`PaneGrid`]: crate::widget::PaneGrid
+ pub fn restore(&mut self) {
+ let _ = self.maximized.take();
+ }
+
+ /// Returns the maximized [`Pane`] of the [`PaneGrid`].
+ ///
+ /// [`PaneGrid`]: crate::widget::PaneGrid
+ pub fn maximized(&self) -> Option<Pane> {
+ self.maximized
+ }
}
/// The internal state of a [`PaneGrid`].
@@ -226,11 +261,13 @@ impl Internal {
let Internal {
layout: a,
last_id: next_id,
+ ..
} = Self::from_configuration(panes, *a, next_id);
let Internal {
layout: b,
last_id: next_id,
+ ..
} = Self::from_configuration(panes, *b, next_id);
(
@@ -304,25 +341,8 @@ impl Action {
}
impl Internal {
- /// Calculates the current [`Pane`] regions from the [`PaneGrid`] layout.
- ///
- /// [`PaneGrid`]: crate::widget::PaneGrid
- pub fn pane_regions(
- &self,
- spacing: f32,
- size: Size,
- ) -> BTreeMap<Pane, Rectangle> {
- self.layout.pane_regions(spacing, size)
- }
-
- /// Calculates the current [`Split`] regions from the [`PaneGrid`] layout.
- ///
- /// [`PaneGrid`]: crate::widget::PaneGrid
- pub fn split_regions(
- &self,
- spacing: f32,
- size: Size,
- ) -> BTreeMap<Split, (Axis, Rectangle, f32)> {
- self.layout.split_regions(spacing, size)
+ /// The layout [`Node`] of the [`Internal`] state
+ pub fn layout(&self) -> &Node {
+ &self.layout
}
}