Allow custom title and ui config in custom layout. (#589)

* Allow custom title and ui config in custom layout.

Adds the following layouts:

- Static
- Dynamic

Deprecates `CustomContent` (but won't be removed to maintain compatibility).

Closes: https://github.com/sayanarijit/xplr/issues/563

* Delete init.lua

* Update docs/en/src/layout.md

* Update docs/en/src/layout.md

* Rename

- Paragraph => CustomParagraph
- List => CustomList
- Table => CustomTable

Also update init.lua

* Fix clippy errs

* Fix doc links
pull/591/head
Arijit Basu 2 years ago committed by GitHub
parent 6595e2ee93
commit 08e1f6aa2d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -20,6 +20,10 @@ A border can be one of the following:
- Double - Double
- Thick - Thick
### Border Style
The [style][1] of the borders.
## Example ## Example
```lua ```lua
@ -28,3 +32,5 @@ xplr.config.general.panel_ui.default.border_type = "Thick"
xplr.config.general.panel_ui.default.border_style.fg = "Black" xplr.config.general.panel_ui.default.border_style.fg = "Black"
xplr.config.general.panel_ui.default.border_style.bg = "Gray" xplr.config.general.panel_ui.default.border_style.bg = "Gray"
``` ```
[1]: style.md#style

@ -32,56 +32,61 @@ A layout can be one of the following:
- [Selection][11] - [Selection][11]
- [HelpMenu][12] - [HelpMenu][12]
- [SortAndFilter][13] - [SortAndFilter][13]
- [CustomContent][25] - [Static][25]
- [Dynamic][26]
- [Horizontal][14] - [Horizontal][14]
- [Vertical][16] - [Vertical][16]
- CustomContent (deprecated, use `Static` or `Dynamic`)
### Nothing ### Nothing
This layout contains a blank panel. This layout contains a blank panel.
Example: "Nothing" Type: "Nothing"
### Table ### Table
This layout contains the table displaying the files and directories in the This layout contains the table displaying the files and directories in the current
current directory. directory.
### InputAndLogs ### InputAndLogs
This layout contains the panel displaying the input prompt and logs. This layout contains the panel displaying the input prompt and logs.
Example: "InputAndLogs" Type: "InputAndLogs"
### Selection ### Selection
This layout contains the panel displaying the selected paths. This layout contains the panel displaying the selected paths.
Example: "Selection" Type: "Selection"
### HelpMenu ### HelpMenu
This layout contains the panel displaying the help menu for the current mode in This layout contains the panel displaying the help menu for the current mode in
real-time. real-time.
Example: "HelpMenu" Type: "HelpMenu"
### SortAndFilter ### SortAndFilter
This layout contains the panel displaying the pipeline of sorters and filters This layout contains the panel displaying the pipeline of sorters and filters applied on
applied of the list of paths being displayed. the list of paths being displayed.
Example: "SortAndFilter" Type: "SortAndFilter"
### Custom Content ### Static
Custom content is a special layout to render something custom. This is a custom layout to render static content.
It contains the following information:
Type: { Static = [Custom Panel][27] }
- [title][33] ### Dynamic
- [body][34]
Example: { CustomContent = { title = [title][33], body = [body][34] } This is a custom layout to render dynamic content using a function defined in
[xplr.fn][28] that takes [Content Renderer Argument][36] and returns [Custom Panel][27].
Type: { Dynamic = [Content Renderer][35] }
### Horizontal ### Horizontal
@ -92,7 +97,7 @@ It contains the following information:
- [config][15] - [config][15]
- [splits][17] - [splits][17]
Example: { Horizontal = { config = [config][15], splits = [splits][17] } Type: { Horizontal = { config = [config][15], splits = [splits][17] }
### Vertical ### Vertical
@ -103,7 +108,7 @@ It contains the following information:
- [config][15] - [config][15]
- [splits][17] - [splits][17]
Example: { Vertical = { config = [config][15], splits = [splits][17] } Type: { Vertical = { config = [config][15], splits = [splits][17] }
## Layout Config ## Layout Config
@ -166,187 +171,153 @@ Type: list of [Layout][3]
The list of child layouts to fit into the parent layout. The list of child layouts to fit into the parent layout.
## title ## Custom Panel
Type: nullable string
The title of the panel.
## body
Type: [Content Body][26]
The body of the panel. Custom panel can be one of the following:
## Content Body - [CustomParagraph][29]
- [CustomList][30]
- [CustomTable][31]
Content body can be one of the following: ### CustomParagraph
- [StaticParagraph][27]
- [DynamicParagraph][28]
- [StaticList][29]
- [DynamicList][30]
- [StaticTable][31]
- [DynamicTable][32]
## Static Paragraph
A paragraph to render. It contains the following fields: A paragraph to render. It contains the following fields:
- **render** (string): The string to render. - **ui** (nullable [Panel UI Config][32]): Optional UI config for the panel.
- **body** (string): The string to render.
#### Example: Render a custom static paragraph #### Example: Render a custom static paragraph
```lua ```lua
xplr.config.layouts.builtin.default = { xplr.config.layouts.builtin.default = {
CustomContent = { Static = {
title = "custom title", CustomParagraph = {
body = { ui = { title = { format = " custom title " } },
StaticParagraph = { render = "custom body" }, body = "custom body",
}, },
}, },
} }
``` ```
## Dynamic Paragraph
A [Lua function][35] to render a custom paragraph.
It contains the following fields:
- **render** (string): The [lua function][35] that returns the paragraph to
render.
#### Example: Render a custom dynamic paragraph #### Example: Render a custom dynamic paragraph
```lua ```lua
xplr.config.layouts.builtin.default = { xplr.config.layouts.builtin.default = { Dynamic = "custom.render_layout" }
CustomContent = {
title = "custom title",
body = { DynamicParagraph = { render = "custom.render_layout" } },
},
}
xplr.fn.custom.render_layout = function(ctx) xplr.fn.custom.render_layout = function(ctx)
return ctx.app.pwd return {
CustomParagraph = {
ui = { title = { format = ctx.app.pwd } },
body = xplr.util.to_yaml(ctx.app.focused_node),
},
}
end end
``` ```
## Static List ### CustomList
A list to render. It contains the following fields: A list to render. It contains the following fields:
- **render** (list of string): The list to render. - **ui** (nullable [Panel UI Config][32]): Optional UI config for the panel.
- **body** (list of string): The list of strings to display.
#### Example: Render a custom static list #### Example: Render a custom static list
```lua ```lua
xplr.config.layouts.builtin.default = { xplr.config.layouts.builtin.default = {
CustomContent = { Static = {
title = "custom title", CustomList = {
body = { ui = { title = { format = " custom title " } },
StaticList = { render = { "1", "2", "3" } }, body = { "1", "2", "3" },
}, },
}, },
} }
``` ```
## Dynamic List
A [Lua function][35] to render a custom list.
It contains the following fields:
- **render** (string): The [lua function][35] that returns the list to render.
#### Example: Render a custom dynamic list #### Example: Render a custom dynamic list
```lua ```lua
xplr.config.layouts.builtin.default = { xplr.config.layouts.builtin.default = { Dynamic = "custom.render_layout" }
CustomContent = {
title = "custom title",
body = { DynamicList = { render = "custom.render_layout" } },
},
}
xplr.fn.custom.render_layout = function(ctx) xplr.fn.custom.render_layout = function(ctx)
return { return {
ctx.app.pwd, CustomList = {
ui = { title = { format = ctx.app.pwd } },
body = {
(ctx.app.focused_node or {}).relative_path or "",
ctx.app.version, ctx.app.version,
tostring(ctx.app.pid), tostring(ctx.app.pid),
},
},
} }
end end
``` ```
## Static Table ## CustomTable
A table to render. It contains the following fields: A custom table to render. It contains the following fields:
- **ui** (nullable [Panel UI Config][32]): Optional UI config for the panel.
- **widths** (list of [Constraint][22]): Width of the columns. - **widths** (list of [Constraint][22]): Width of the columns.
- **col_spacing** (nullable int): Spacing between columns. Defaults to 1. - **col_spacing** (nullable int): Spacing between columns. Defaults to 1.
- **render** (list of list of string): The rows and columns to render. - **body** (list of list of string): The rows and columns to render.
#### Example: Render a custom static table #### Example: Render a custom static table
```lua ```lua
xplr.config.layouts.builtin.default = { xplr.config.layouts.builtin.default = {
CustomContent = { Static = {
title = "custom title", CustomTable = {
body = { ui = { title = { format = " custom title " } },
StaticTable = {
widths = { widths = {
{ Percentage = 50 }, { Percentage = 50 },
{ Percentage = 50 }, { Percentage = 50 },
}, },
col_spacing = 1, body = {
render = {
{ "a", "b" }, { "a", "b" },
{ "c", "d" }, { "c", "d" },
}, },
}, },
}, },
},
} }
``` ```
## Dynamic Table
A [Lua function][35] to render a custom table.
It contains the following fields:
- **widths** (list of [Constraint][22]): Width of the columns.
- **col_spacing** (nullable int): Spacing between columns. Defaults to 1.
- **render** (string): The [lua function][35] that returns the table to render.
#### Example: Render a custom dynamic table #### Example: Render a custom dynamic table
```lua ```lua
xplr.config.layouts.builtin.default = { xplr.config.layouts.builtin.default = {Dynamic = "custom.render_layout" }
CustomContent = {
title = "custom title", xplr.fn.custom.render_layout = function(ctx)
body = { return {
DynamicTable = { CustomTable = {
ui = { title = { format = ctx.app.pwd } },
widths = { widths = {
{ Percentage = 50 }, { Percentage = 50 },
{ Percentage = 50 }, { Percentage = 50 },
}, },
col_spacing = 1, body = {
render = "custom.render_layout",
},
},
},
}
xplr.fn.custom.render_layout = function(ctx)
return {
{ "", "" }, { "", "" },
{ "Layout height", tostring(ctx.layout_size.height) }, { "Layout height", tostring(ctx.layout_size.height) },
{ "Layout width", tostring(ctx.layout_size.width) }, { "Layout width", tostring(ctx.layout_size.width) },
{ "", "" }, { "", "" },
{ "Screen height", tostring(ctx.screen_size.height) }, { "Screen height", tostring(ctx.screen_size.height) },
{ "Screen width", tostring(ctx.screen_size.width) }, { "Screen width", tostring(ctx.screen_size.width) },
},
},
} }
end end
``` ```
## Panel UI Config
It contains the following optional fields:
- **title** ({ format = "string", style = [Style][33] }): the title of the panel.
- **style** ([Style][33]): The style of the panel body.
- **borders** (nullable list of [Border][34]): The shape of the borders.
- **border_type** ([Border Type][54]): The type of the borders.
- **border_style** ([Style][33]): The style of the borders.
## Content Renderer ## Content Renderer
It is a Lua function that receives [a special argument][36] as input and It is a Lua function that receives [a special argument][36] as input and
@ -421,16 +392,16 @@ Hence, only the following fields are avilable.
[22]: #constraint [22]: #constraint
[23]: https://s6.gifyu.com/images/layout.png [23]: https://s6.gifyu.com/images/layout.png
[24]: https://gifyu.com/image/1X38 [24]: https://gifyu.com/image/1X38
[25]: #custom-content [25]: #static
[26]: #content-body [26]: #dynamic
[27]: #static-paragraph [27]: #custom-panel
[28]: #dynamic-paragraph [28]: configuration.md#function
[29]: #static-list [29]: #customparagraph
[30]: #dynamic-list [30]: #customlist
[31]: #static-table [31]: #customtable
[32]: #dynamic-table [32]: #panel-ui-config
[33]: #title [33]: style.md#style
[34]: #body [34]: borders.md#border
[35]: #content-renderer [35]: #content-renderer
[36]: #content-renderer-argument [36]: #content-renderer-argument
[37]: #size [37]: #size
@ -450,3 +421,4 @@ Hence, only the following fields are avilable.
[51]: layouts.md [51]: layouts.md
[52]: lua-function-calls.md#vroot [52]: lua-function-calls.md#vroot
[53]: lua-function-calls.md#initial_pwd [53]: lua-function-calls.md#initial_pwd
[54]: borders.md#border-type

@ -0,0 +1,224 @@
// Things of the past, mostly bad decisions, which cannot erased, stays in this
// haunted module.
use crate::app;
use crate::lua;
use crate::ui::block;
use crate::ui::string_to_text;
use crate::ui::Constraint;
use crate::ui::ContentRendererArg;
use mlua::Lua;
use serde::{Deserialize, Serialize};
use tui::backend::Backend;
use tui::layout::Constraint as TuiConstraint;
use tui::layout::Rect as TuiRect;
use tui::widgets::Cell;
use tui::widgets::List;
use tui::widgets::ListItem;
use tui::widgets::Paragraph;
use tui::widgets::Row;
use tui::widgets::Table;
use tui::Frame;
/// A cursed enum from crate::ui.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(deny_unknown_fields)]
pub enum ContentBody {
/// A paragraph to render
StaticParagraph { render: String },
/// A Lua function that returns a paragraph to render
DynamicParagraph { render: String },
/// List to render
StaticList { render: Vec<String> },
/// A Lua function that returns lines to render
DynamicList { render: String },
/// A table to render
StaticTable {
widths: Vec<Constraint>,
col_spacing: Option<u16>,
render: Vec<Vec<String>>,
},
/// A Lua function that returns a table to render
DynamicTable {
widths: Vec<Constraint>,
col_spacing: Option<u16>,
render: String,
},
}
/// A cursed struct from crate::ui.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(deny_unknown_fields)]
pub struct CustomContent {
pub title: Option<String>,
pub body: ContentBody,
}
/// A cursed function from crate::ui.
pub fn draw_custom_content<B: Backend>(
f: &mut Frame<B>,
screen_size: TuiRect,
layout_size: TuiRect,
app: &app::App,
content: CustomContent,
lua: &Lua,
) {
let config = app.config.general.panel_ui.default.clone();
let title = content.title;
let body = content.body;
match body {
ContentBody::StaticParagraph { render } => {
let render = string_to_text(render);
let content = Paragraph::new(render).block(block(
config,
title.map(|t| format!(" {t} ")).unwrap_or_default(),
));
f.render_widget(content, layout_size);
}
ContentBody::DynamicParagraph { render } => {
let ctx = ContentRendererArg {
app: app.to_lua_ctx_light(),
layout_size: layout_size.into(),
screen_size: screen_size.into(),
};
let render = lua::serialize(lua, &ctx)
.map(|arg| {
lua::call(lua, &render, arg).unwrap_or_else(|e| format!("{e:?}"))
})
.unwrap_or_else(|e| e.to_string());
let render = string_to_text(render);
let content = Paragraph::new(render).block(block(
config,
title.map(|t| format!(" {t} ")).unwrap_or_default(),
));
f.render_widget(content, layout_size);
}
ContentBody::StaticList { render } => {
let items = render
.into_iter()
.map(string_to_text)
.map(ListItem::new)
.collect::<Vec<ListItem>>();
let content = List::new(items).block(block(
config,
title.map(|t| format!(" {t} ")).unwrap_or_default(),
));
f.render_widget(content, layout_size);
}
ContentBody::DynamicList { render } => {
let ctx = ContentRendererArg {
app: app.to_lua_ctx_light(),
layout_size: layout_size.into(),
screen_size: screen_size.into(),
};
let items = lua::serialize(lua, &ctx)
.map(|arg| {
lua::call(lua, &render, arg)
.unwrap_or_else(|e| vec![format!("{e:?}")])
})
.unwrap_or_else(|e| vec![e.to_string()])
.into_iter()
.map(string_to_text)
.map(ListItem::new)
.collect::<Vec<ListItem>>();
let content = List::new(items).block(block(
config,
title.map(|t| format!(" {t} ")).unwrap_or_default(),
));
f.render_widget(content, layout_size);
}
ContentBody::StaticTable {
widths,
col_spacing,
render,
} => {
let rows = render
.into_iter()
.map(|cols| {
Row::new(
cols.into_iter()
.map(string_to_text)
.map(Cell::from)
.collect::<Vec<Cell>>(),
)
})
.collect::<Vec<Row>>();
let widths = widths
.into_iter()
.map(|w| w.to_tui(screen_size, layout_size))
.collect::<Vec<TuiConstraint>>();
let content = Table::new(rows)
.widths(&widths)
.column_spacing(col_spacing.unwrap_or(1))
.block(block(
config,
title.map(|t| format!(" {t} ")).unwrap_or_default(),
));
f.render_widget(content, layout_size);
}
ContentBody::DynamicTable {
widths,
col_spacing,
render,
} => {
let ctx = ContentRendererArg {
app: app.to_lua_ctx_light(),
layout_size: layout_size.into(),
screen_size: screen_size.into(),
};
let rows = lua::serialize(lua, &ctx)
.map(|arg| {
lua::call(lua, &render, arg)
.unwrap_or_else(|e| vec![vec![format!("{e:?}")]])
})
.unwrap_or_else(|e| vec![vec![e.to_string()]])
.into_iter()
.map(|cols| {
Row::new(
cols.into_iter()
.map(string_to_text)
.map(Cell::from)
.collect::<Vec<Cell>>(),
)
})
.collect::<Vec<Row>>();
let widths = widths
.into_iter()
.map(|w| w.to_tui(screen_size, layout_size))
.collect::<Vec<TuiConstraint>>();
let mut content = Table::new(rows).widths(&widths).block(block(
config,
title.map(|t| format!(" {t} ")).unwrap_or_default(),
));
if let Some(col_spacing) = col_spacing {
content = content.column_spacing(col_spacing);
};
f.render_widget(content, layout_size);
}
}
}

@ -1317,11 +1317,10 @@ xplr.config.modes.builtin.debug_error = {
}, },
splits = { splits = {
{ {
CustomContent = { Static = {
title = "debug error", CustomParagraph = {
body = { ui = { title = { format = "debug error" } },
StaticParagraph = { body = [[
render = [[
Some errors occurred during startup. Some errors occurred during startup.
If you think this is a bug, please report it at: If you think this is a bug, please report it at:
@ -1337,7 +1336,6 @@ xplr.config.modes.builtin.debug_error = {
}, },
}, },
}, },
},
"InputAndLogs", "InputAndLogs",
}, },
}, },
@ -1373,11 +1371,10 @@ xplr.config.modes.builtin.debug_error = {
xplr.config.modes.builtin.recover = { xplr.config.modes.builtin.recover = {
name = "recover", name = "recover",
layout = { layout = {
CustomContent = { Static = {
title = " recover ", CustomParagraph = {
body = { ui = { title = { format = "recover" } },
StaticParagraph = { body = [[
render = [[
You pressed an invalid key and went into "recover" mode. You pressed an invalid key and went into "recover" mode.
This mode saves you from performing unwanted actions. This mode saves you from performing unwanted actions.
@ -1390,7 +1387,6 @@ xplr.config.modes.builtin.recover = {
}, },
}, },
}, },
},
key_bindings = { key_bindings = {
default = { default = {
messages = {}, messages = {},

@ -4,6 +4,7 @@
pub mod app; pub mod app;
pub mod cli; pub mod cli;
pub mod compat;
pub mod config; pub mod config;
pub mod directory_buffer; pub mod directory_buffer;
pub mod event_reader; pub mod event_reader;

@ -1,5 +1,6 @@
use crate::app::{HelpMenuLine, NodeFilterApplicable, NodeSorterApplicable}; use crate::app::{HelpMenuLine, NodeFilterApplicable, NodeSorterApplicable};
use crate::app::{Node, ResolvedNode}; use crate::app::{Node, ResolvedNode};
use crate::compat::{draw_custom_content, CustomContent};
use crate::config::PanelUiConfig; use crate::config::PanelUiConfig;
use crate::lua; use crate::lua;
use crate::permissions::Permissions; use crate::permissions::Permissions;
@ -38,7 +39,7 @@ fn read_only_indicator(app: &app::App) -> &str {
} }
} }
fn string_to_text<'a>(string: String) -> Text<'a> { pub fn string_to_text<'a>(string: String) -> Text<'a> {
if *NO_COLOR { if *NO_COLOR {
Text::raw(string) Text::raw(string)
} else { } else {
@ -77,31 +78,23 @@ impl LayoutOptions {
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(deny_unknown_fields)] #[serde(deny_unknown_fields)]
pub enum ContentBody { pub enum CustomPanel {
/// A paragraph to render CustomParagraph {
StaticParagraph { render: String }, #[serde(default)]
ui: PanelUiConfig,
/// A Lua function that returns a paragraph to render body: String,
DynamicParagraph { render: String },
/// List to render
StaticList { render: Vec<String> },
/// A Lua function that returns lines to render
DynamicList { render: String },
/// A table to render
StaticTable {
widths: Vec<Constraint>,
col_spacing: Option<u16>,
render: Vec<Vec<String>>,
}, },
CustomList {
/// A Lua function that returns a table to render #[serde(default)]
DynamicTable { ui: PanelUiConfig,
body: Vec<String>,
},
CustomTable {
#[serde(default)]
ui: PanelUiConfig,
widths: Vec<Constraint>, widths: Vec<Constraint>,
col_spacing: Option<u16>, col_spacing: Option<u16>,
render: String, body: Vec<Vec<String>>,
}, },
} }
@ -114,10 +107,8 @@ pub enum Layout {
Selection, Selection,
HelpMenu, HelpMenu,
SortAndFilter, SortAndFilter,
CustomContent { Static(Box<CustomPanel>),
title: Option<String>, Dynamic(String),
body: ContentBody,
},
Horizontal { Horizontal {
config: LayoutOptions, config: LayoutOptions,
splits: Vec<Layout>, splits: Vec<Layout>,
@ -126,6 +117,9 @@ pub enum Layout {
config: LayoutOptions, config: LayoutOptions,
splits: Vec<Layout>, splits: Vec<Layout>,
}, },
/// For compatibility only. A better choice is Static or Dymanic layout.
CustomContent(Box<CustomContent>),
} }
impl Default for Layout { impl Default for Layout {
@ -640,7 +634,7 @@ impl NodeUiMetadata {
} }
} }
fn block<'a>(config: PanelUiConfig, default_title: String) -> Block<'a> { pub fn block<'a>(config: PanelUiConfig, default_title: String) -> Block<'a> {
Block::default() Block::default()
.borders(TuiBorders::from_bits_truncate( .borders(TuiBorders::from_bits_truncate(
config config
@ -770,7 +764,7 @@ fn draw_table<B: Backend>(
.filter_map(|c| { .filter_map(|c| {
c.format.as_ref().map(|f| { c.format.as_ref().map(|f| {
let out = lua::call(lua, f, v.clone()) let out = lua::call(lua, f, v.clone())
.unwrap_or_else(|e| e.to_string()); .unwrap_or_else(|e| format!("{e:?}"));
string_to_text(out) string_to_text(out)
}) })
}) })
@ -876,8 +870,8 @@ fn draw_selection<B: Backend>(
.as_ref() .as_ref()
.map(|f| { .map(|f| {
lua::serialize::<Node>(lua, n) lua::serialize::<Node>(lua, n)
.map(|n| lua::call(lua, f, n).unwrap_or_else(|e| e.to_string())) .and_then(|n| lua::call(lua, f, n))
.unwrap_or_else(|e| e.to_string()) .unwrap_or_else(|e| format!("{e:?}"))
}) })
.unwrap_or_else(|| n.absolute_path.clone()); .unwrap_or_else(|| n.absolute_path.clone());
string_to_text(out) string_to_text(out)
@ -1211,94 +1205,68 @@ pub fn draw_nothing<B: Backend>(
f.render_widget(nothing, layout_size); f.render_widget(nothing, layout_size);
} }
pub fn draw_custom_content<B: Backend>( pub fn draw_dynamic<B: Backend>(
f: &mut Frame<B>, f: &mut Frame<B>,
screen_size: TuiRect, screen_size: TuiRect,
layout_size: TuiRect, layout_size: TuiRect,
app: &app::App, app: &app::App,
title: Option<String>, func: &str,
body: ContentBody,
lua: &Lua, lua: &Lua,
) { ) {
let config = app.config.general.panel_ui.default.clone();
match body {
ContentBody::StaticParagraph { render } => {
let render = string_to_text(render);
let content = Paragraph::new(render).block(block(
config,
title.map(|t| format!(" {t} ")).unwrap_or_default(),
));
f.render_widget(content, layout_size);
}
ContentBody::DynamicParagraph { render } => {
let ctx = ContentRendererArg { let ctx = ContentRendererArg {
app: app.to_lua_ctx_light(), app: app.to_lua_ctx_light(),
layout_size: layout_size.into(), layout_size: layout_size.into(),
screen_size: screen_size.into(), screen_size: screen_size.into(),
}; };
let render = lua::serialize(lua, &ctx) let panel: CustomPanel = lua::serialize(lua, &ctx)
.map(|arg| { .and_then(|arg| lua::call(lua, func, arg))
lua::call(lua, &render, arg).unwrap_or_else(|e| format!("{e:?}")) .unwrap_or_else(|e| CustomPanel::CustomParagraph {
}) ui: app.config.general.panel_ui.default.clone(),
.unwrap_or_else(|e| e.to_string()); body: format!("{e:?}"),
});
let render = string_to_text(render); draw_static(f, screen_size, layout_size, app, panel, lua);
}
let content = Paragraph::new(render).block(block( pub fn draw_static<B: Backend>(
config, f: &mut Frame<B>,
title.map(|t| format!(" {t} ")).unwrap_or_default(), screen_size: TuiRect,
)); layout_size: TuiRect,
app: &app::App,
panel: CustomPanel,
_lua: &Lua,
) {
let defaultui = app.config.general.panel_ui.default.clone();
match panel {
CustomPanel::CustomParagraph { ui, body } => {
let config = defaultui.extend(&ui);
let body = string_to_text(body);
let content = Paragraph::new(body).block(block(config, "".into()));
f.render_widget(content, layout_size); f.render_widget(content, layout_size);
} }
ContentBody::StaticList { render } => { CustomPanel::CustomList { ui, body } => {
let items = render let config = defaultui.extend(&ui);
.into_iter()
.map(string_to_text)
.map(ListItem::new)
.collect::<Vec<ListItem>>();
let content = List::new(items).block(block(
config,
title.map(|t| format!(" {t} ")).unwrap_or_default(),
));
f.render_widget(content, layout_size);
}
ContentBody::DynamicList { render } => { let items = body
let ctx = ContentRendererArg {
app: app.to_lua_ctx_light(),
layout_size: layout_size.into(),
screen_size: screen_size.into(),
};
let items = lua::serialize(lua, &ctx)
.map(|arg| {
lua::call(lua, &render, arg)
.unwrap_or_else(|e| vec![format!("{e:?}")])
})
.unwrap_or_else(|e| vec![e.to_string()])
.into_iter() .into_iter()
.map(string_to_text) .map(string_to_text)
.map(ListItem::new) .map(ListItem::new)
.collect::<Vec<ListItem>>(); .collect::<Vec<ListItem>>();
let content = List::new(items).block(block( let content = List::new(items).block(block(config, "".into()));
config,
title.map(|t| format!(" {t} ")).unwrap_or_default(),
));
f.render_widget(content, layout_size); f.render_widget(content, layout_size);
} }
ContentBody::StaticTable { CustomPanel::CustomTable {
ui,
widths, widths,
col_spacing, col_spacing,
render, body,
} => { } => {
let rows = render let config = defaultui.extend(&ui);
let rows = body
.into_iter() .into_iter()
.map(|cols| { .map(|cols| {
Row::new( Row::new(
@ -1318,55 +1286,7 @@ pub fn draw_custom_content<B: Backend>(
let content = Table::new(rows) let content = Table::new(rows)
.widths(&widths) .widths(&widths)
.column_spacing(col_spacing.unwrap_or(1)) .column_spacing(col_spacing.unwrap_or(1))
.block(block( .block(block(config, "".into()));
config,
title.map(|t| format!(" {t} ")).unwrap_or_default(),
));
f.render_widget(content, layout_size);
}
ContentBody::DynamicTable {
widths,
col_spacing,
render,
} => {
let ctx = ContentRendererArg {
app: app.to_lua_ctx_light(),
layout_size: layout_size.into(),
screen_size: screen_size.into(),
};
let rows = lua::serialize(lua, &ctx)
.map(|arg| {
lua::call(lua, &render, arg)
.unwrap_or_else(|e| vec![vec![format!("{e:?}")]])
})
.unwrap_or_else(|e| vec![vec![e.to_string()]])
.into_iter()
.map(|cols| {
Row::new(
cols.into_iter()
.map(string_to_text)
.map(Cell::from)
.collect::<Vec<Cell>>(),
)
})
.collect::<Vec<Row>>();
let widths = widths
.into_iter()
.map(|w| w.to_tui(screen_size, layout_size))
.collect::<Vec<TuiConstraint>>();
let mut content = Table::new(rows).widths(&widths).block(block(
config,
title.map(|t| format!(" {t} ")).unwrap_or_default(),
));
if let Some(col_spacing) = col_spacing {
content = content.column_spacing(col_spacing);
};
f.render_widget(content, layout_size); f.render_widget(content, layout_size);
} }
@ -1394,9 +1314,9 @@ impl From<TuiRect> for Rect {
#[derive(Debug, Clone, Serialize)] #[derive(Debug, Clone, Serialize)]
pub struct ContentRendererArg { pub struct ContentRendererArg {
app: app::LuaContextLight, pub app: app::LuaContextLight,
screen_size: Rect, pub screen_size: Rect,
layout_size: Rect, pub layout_size: Rect,
} }
pub fn draw_layout<B: Backend>( pub fn draw_layout<B: Backend>(
@ -1422,8 +1342,14 @@ pub fn draw_layout<B: Backend>(
draw_logs(f, screen_size, layout_size, app, lua); draw_logs(f, screen_size, layout_size, app, lua);
}; };
} }
Layout::CustomContent { title, body } => { Layout::Static(panel) => {
draw_custom_content(f, screen_size, layout_size, app, title, body, lua) draw_static(f, screen_size, layout_size, app, *panel, lua)
}
Layout::Dynamic(ref func) => {
draw_dynamic(f, screen_size, layout_size, app, func, lua)
}
Layout::CustomContent(content) => {
draw_custom_content(f, screen_size, layout_size, app, *content, lua)
} }
Layout::Horizontal { config, splits } => { Layout::Horizontal { config, splits } => {
let chunks = TuiLayout::default() let chunks = TuiLayout::default()

Loading…
Cancel
Save