Skip to content

Commit

Permalink
Save zoom and focused window from layout so that it can be restored (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
Andrew-Collins authored Aug 10, 2024
1 parent 7d8a6c3 commit b71afe6
Show file tree
Hide file tree
Showing 6 changed files with 181 additions and 14 deletions.
15 changes: 14 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/modalkit-ratatui/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,4 @@ serde = { version = "^1.0", features = ["derive"] }

[dev-dependencies]
rand = { workspace = true }
serde_json = "1.0.122"
27 changes: 19 additions & 8 deletions crates/modalkit-ratatui/src/screen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ use ratatui::{
use super::{
cmdbar::{CommandBar, CommandBarState},
util::{rect_down, rect_zero_height},
windows::{WindowActions, WindowLayout, WindowLayoutDescription, WindowLayoutState},
windows::{WindowActions, WindowLayout, WindowLayoutRoot, WindowLayoutState},
TerminalCursor,
Window,
WindowOps,
Expand Down Expand Up @@ -244,23 +244,33 @@ fn bold<'a>(s: String) -> Span<'a> {
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(bound(deserialize = "I::WindowId: Deserialize<'de>"))]
#[serde(bound(serialize = "I::WindowId: Serialize"))]
pub struct TabLayoutDescription<I: ApplicationInfo> {
pub struct TabbedLayoutDescription<I: ApplicationInfo> {
/// The description of the window layout for each tab.
pub tabs: Vec<WindowLayoutDescription<I>>,
pub tabs: Vec<WindowLayoutRoot<I>>,
/// The index of the last focused tab
pub focused: usize,
}

impl<I: ApplicationInfo> TabLayoutDescription<I> {
impl<I: ApplicationInfo> TabbedLayoutDescription<I> {
/// Create a new collection of tabs from this description.
pub fn to_layout<W: Window<I>>(
self,
area: Option<Rect>,
store: &mut Store<I>,
) -> UIResult<FocusList<WindowLayoutState<W, I>>, I> {
self.tabs
let mut tabs = self
.tabs
.into_iter()
.map(|desc| desc.to_layout(area, store))
.collect::<UIResult<Vec<_>, I>>()
.map(FocusList::new)
.map(FocusList::new)?;

// Count starts at 1
let change = FocusChange::Offset(Count::Exact(self.focused + 1), true);
let ctx = EditContext::default();
tabs.focus(&change, &ctx);

Ok(tabs)
}
}

Expand Down Expand Up @@ -314,9 +324,10 @@ where
}

/// Get a description of the open tabs and their window layouts.
pub fn as_description(&self) -> TabLayoutDescription<I> {
TabLayoutDescription {
pub fn as_description(&self) -> TabbedLayoutDescription<I> {
TabbedLayoutDescription {
tabs: self.tabs.iter().map(WindowLayoutState::as_description).collect(),
focused: self.tabs.pos(),
}
}

Expand Down
54 changes: 50 additions & 4 deletions crates/modalkit-ratatui/src/windows/layout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1102,6 +1102,34 @@ where
}
}

/// Data structure holding layout description and state
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(bound(deserialize = "I::WindowId: Deserialize<'de>"))]
#[serde(bound(serialize = "I::WindowId: Serialize"))]
#[serde(rename_all = "lowercase")]
pub struct WindowLayoutRoot<I: ApplicationInfo> {
layout: WindowLayoutDescription<I>,
focused: usize,
zoomed: bool,
}

impl<I> WindowLayoutRoot<I>
where
I: ApplicationInfo,
{
/// Restore a layout from a description of windows and splits.
pub fn to_layout<W: Window<I>>(
self,
area: Option<Rect>,
store: &mut Store<I>,
) -> UIResult<WindowLayoutState<W, I>, I> {
let mut layout = self.layout.to_layout(area, store)?;
layout._focus(self.focused);
layout.zoom = self.zoomed;
Ok(layout)
}
}

/// A description of a window layout.
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(bound(deserialize = "I::WindowId: Deserialize<'de>"))]
Expand Down Expand Up @@ -1282,18 +1310,28 @@ where
}

/// Convert this layout to a serializable summary of its windows and splits.
pub fn as_description(&self) -> WindowLayoutDescription<I> {
pub fn as_description(&self) -> WindowLayoutRoot<I> {
let mut children = vec![];
let focused = self.focused;
let zoomed = self.zoom;

let Some(root) = &self.root else {
return WindowLayoutDescription::Split { children, length: None };
return WindowLayoutRoot {
layout: WindowLayoutDescription::Split { children, length: None },
focused,
zoomed,
};
};

for w in root.iter() {
children.push(w.into());
}

return WindowLayoutDescription::Split { children, length: None };
return WindowLayoutRoot {
layout: WindowLayoutDescription::Split { children, length: None },
focused,
zoomed,
};
}

/// Create a new instance containing a single [Window] displaying some content.
Expand Down Expand Up @@ -2808,6 +2846,7 @@ mod tests {
let (mut tree, mut store, _) = three_by_three();
let mut buffer = Buffer::empty(Rect::new(0, 0, 60, 60));
let area = Rect::new(0, 0, 60, 60);
tree._focus(3);

// Draw so that everything gets an initial area.
WindowLayout::new(&mut store).render(area, &mut buffer, &mut tree);
Expand Down Expand Up @@ -2855,13 +2894,20 @@ mod tests {
}],
length: None,
};
assert_eq!(desc1, exp);
assert_eq!(desc1.layout, exp);
assert_eq!(desc1.focused, 3);
assert_eq!(desc1.zoomed, false);

// Turn back into a layout, and then generate a new description to show it's the same.
let tree = desc1
.clone()
.to_layout::<TestWindow>(tree.info.area.into(), &mut store)
.unwrap();
assert_eq!(tree.as_description(), desc1);

// Test against an example JSON serialization to test naming.
let serialized = serde_json::to_string_pretty(&desc1).unwrap();
let exp = include_str!("../../tests/window-layout.json");
assert_eq!(serialized, exp.trim_end());
}
}
7 changes: 6 additions & 1 deletion crates/modalkit-ratatui/src/windows/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,12 @@ mod size;
mod slot;
mod tree;

pub use self::layout::{WindowLayout, WindowLayoutDescription, WindowLayoutState};
pub use self::layout::{
WindowLayout,
WindowLayoutDescription,
WindowLayoutRoot,
WindowLayoutState,
};

struct AxisTreeNode<W, X: AxisT, Y: AxisT> {
value: Value<W, X, Y>,
Expand Down
91 changes: 91 additions & 0 deletions crates/modalkit-ratatui/tests/window-layout.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
{
"layout": {
"type": "split",
"children": [
{
"type": "split",
"children": [
{
"type": "split",
"children": [
{
"type": "split",
"children": [
{
"type": "window",
"window": 0,
"length": 20
},
{
"type": "window",
"window": 1,
"length": 20
}
],
"length": 20
},
{
"type": "split",
"children": [
{
"type": "window",
"window": 2,
"length": 20
},
{
"type": "window",
"window": 3,
"length": 20
}
],
"length": 20
},
{
"type": "split",
"children": [
{
"type": "window",
"window": 4,
"length": 20
},
{
"type": "window",
"window": 5,
"length": 20
}
],
"length": 20
}
],
"length": 40
},
{
"type": "split",
"children": [
{
"type": "window",
"window": 6,
"length": 20
},
{
"type": "window",
"window": 7,
"length": 20
},
{
"type": "window",
"window": 8,
"length": 20
}
],
"length": 20
}
],
"length": 60
}
],
"length": null
},
"focused": 3,
"zoomed": false
}

0 comments on commit b71afe6

Please sign in to comment.