Skip to content

Commit 47b71dd

Browse files
committed
Added tags TopPanel, SidePanel, BottomPanel
1 parent 0bf24f5 commit 47b71dd

9 files changed

Lines changed: 673 additions & 2 deletions

File tree

Makefile

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,5 +42,8 @@ dump: ## Make dump of project
4242
! -path "./target/*" \
4343
-exec sh -c 'echo ">>> START {}"; cat "{}"; echo ">>> END {}"; echo ""' \; > efx_code.dump.txt
4444

45+
sandbox: ## Run sandbox on local machine
46+
@cargo run -p efx-sandbox
47+
4548
help: ## Outputs this help screen
4649
@grep -E '(^[a-zA-Z0-9\./_-]+:.*?##.*$$)|(^##)' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}{printf "\033[32m%-30s\033[0m %s\n", $$1, $$2}' | sed -e 's/\[32m##/[33m/'

efx-sandbox/src/main.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,19 @@ impl eframe::App for App {
7373
</ScrollArea>
7474
"##
7575
);
76+
77+
// SidePanel with other tags
78+
let _ = efx!(ui, r##"
79+
<SidePanel side="left" id="nav" default-width="240" min-width="160" resizable="true" fill="#15151A">
80+
<Column gap="8" padding="8">
81+
<Label size="16" bold="true">Navigation</Label>
82+
<Separator/>
83+
<Button frame="false">Home</Button>
84+
<Button frame="false">Projects</Button>
85+
<Button frame="false">Settings</Button>
86+
</Column>
87+
</SidePanel>
88+
"##);
7689
});
7790
}
7891
}

efx/Changelog.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@
22

33
#### 0.6
44
- New Tags: Heading, Image, Grid
5-
- Added Panel Tags: Window, SidePanel
6-
- Added examples & tests
5+
- Added Panel Tags: Window, SidePanel, TopPanel, BottomPanel
6+
- Sandbox
7+
- Examples & tests
78

89
#### 0.5
910
- Attribute rendering (efx-core)

efx/docs/tags.md

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,109 @@ efx!(Ui::default(), r##"
200200
"##);
201201
```
202202

203+
### `<SidePanel>`
204+
205+
Docked panel attached to the left or right edge of the window.
206+
Typically used for navigation, tool palettes, or context inspectors.
207+
208+
**Children:** rendered inside the panel.
209+
210+
**Required attributes**
211+
- `side="left|right"` — which edge to dock to.
212+
- `id="string"` — egui `Id` salt to keep layout state (width, resize state).
213+
214+
**Frame & styling**
215+
- `frame="true|false"` — enable/disable the default frame (default: `true`).
216+
- `fill="#RRGGBB[AA]"` — background color.
217+
- `stroke-width="number"` — border width, in points.
218+
- `stroke-color="#RRGGBB[AA]"` — border color.
219+
- `padding`, `padding-left|right|top|bottom` — inner margin (content padding).
220+
- `margin`, `margin-left|right|top|bottom` — outer margin.
221+
222+
**Sizing & behavior**
223+
- `default-width="number"` — initial width.
224+
- `min-width="number"` — lower width bound.
225+
- `max-width="number"` — upper width bound.
226+
- `resizable="true|false"` — whether the user can drag to resize (default: `true`).
227+
228+
**Example**
229+
```xml
230+
<SidePanel side="left" id="nav" default-width="240" min-width="160" resizable="true" fill="#15151A">
231+
<Column gap="8" padding="8">
232+
<Label size="16" bold="true">Navigation</Label>
233+
<Separator/>
234+
<Button frame="false">Home</Button>
235+
<Button frame="false">Projects</Button>
236+
<Button frame="false">Settings</Button>
237+
</Column>
238+
</SidePanel>
239+
```
240+
241+
### `<TopPanel>`
242+
243+
A docked panel attached to the top edge of the window.
244+
Useful for app bars, toolbars, status strips, or context headers.
245+
246+
**Children:** rendered inside the panel.
247+
248+
**Required attributes**
249+
- `id="string"` — egui `Id` salt to persist panel state.
250+
251+
**Frame & styling**
252+
- `frame="true|false"` — enable/disable default frame (default: `true`).
253+
- `fill="#RRGGBB[AA]"` — background color.
254+
- `stroke-width="number"` — border width (points).
255+
- `stroke-color="#RRGGBB[AA]"` — border color.
256+
- `padding`, `padding-left|right|top|bottom` — inner margin.
257+
- `margin`, `margin-left|right|top|bottom` — outer margin.
258+
259+
**Sizing & behavior**
260+
- `default-height="number"` — initial height.
261+
- `min-height="number"` — minimum height.
262+
- `max-height="number"` — maximum height.
263+
- `resizable="true|false"` — allow user resize (default: `true`).
264+
265+
**Example**
266+
```xml
267+
<TopPanel id="appbar" default-height="36" fill="#15151A" stroke-width="1" stroke-color="#262A33">
268+
<Row gap="8" padding="6">
269+
<Label bold="true">EFx App</Label>
270+
<Separator/>
271+
<Button frame="false">File</Button>
272+
<Button frame="false">Edit</Button>
273+
<Button frame="false">View</Button>
274+
</Row>
275+
</TopPanel>
276+
```
277+
278+
### `<BottomPanel>`
279+
280+
A docked panel attached to the bottom edge of the window.
281+
Great for logs, consoles, timelines, or status bars.
282+
283+
**Children**: rendered inside the panel.
284+
285+
**Required attributes**
286+
- `id="string"` — egui Id salt.
287+
288+
**Frame & styling**
289+
- `frame="true|false"`, `fill`, `stroke-width`, `stroke-color`, `padding*` / `margin*` — same as `<TopPanel>`.
290+
291+
**Sizing & behavior**
292+
- `default-height`, `min-height`, `max-height`, `resizable` — same as <TopPanel>.
293+
294+
**Example**
295+
```xml
296+
<BottomPanel id="console" default-height="200" resizable="true" fill="#0F1116">
297+
<ScrollArea axis="vertical" max_height="180" id="console-scroll">
298+
<Column gap="4" padding="6">
299+
<Label monospace="true">[12:00:01] Ready.</Label>
300+
<Label monospace="true">[12:00:02] Build succeeded.</Label>
301+
</Column>
302+
</ScrollArea>
303+
</BottomPanel>
304+
```
305+
203306
### `ScrollArea`
204307

205308
Scrollable container backed by `egui::ScrollArea`. Wraps its children and provides vertical/horizontal/both scrolling.

efx/src/render.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ fn render_element_stmt<UI: ToTokens>(ui: &UI, el: &Element) -> TokenStream {
3939
match el.name.as_str() {
4040
"Heading" => render_tag::<Heading>(ui, el),
4141
"CentralPanel" => render_tag::<CentralPanel>(ui, el),
42+
"SidePanel" => render_tag::<SidePanel>(ui, el),
43+
"TopPanel" => render_tag::<TopPanel>(ui, el),
44+
"BottomPanel" => render_tag::<BottomPanel>(ui, el),
4245
"ScrollArea" => render_tag::<ScrollArea>(ui, el),
4346
"Row" => render_tag::<Row>(ui, el),
4447
"Column" => render_tag::<Column>(ui, el),

efx/src/tags/bottom_panel.rs

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
use proc_macro2::TokenStream;
2+
use quote::{quote, ToTokens};
3+
use efx_attrnames::AttrNames;
4+
use efx_core::Element;
5+
use crate::tags::{Tag, TagAttributes};
6+
use crate::utils::attr::*;
7+
use crate::utils::render::render_children_stmt;
8+
9+
pub struct BottomPanel {
10+
attributes: Attributes,
11+
element: Element,
12+
}
13+
14+
impl Tag for BottomPanel {
15+
fn from_element(el: &Element) -> Result<Self, TokenStream>
16+
where
17+
Self: Sized
18+
{
19+
Ok(Self { attributes: Attributes::new(el)?, element: el.clone() })
20+
}
21+
22+
fn content<UI: ToTokens>(&self, _ui: &UI) -> TokenStream {
23+
let mut ts = TokenStream::new();
24+
25+
ts.extend(match self.attributes.frame {
26+
Some(false) => quote!( let mut __efx_frame = egui::Frame::none(); ),
27+
_ => quote!( let mut __efx_frame = egui::Frame::default(); ),
28+
});
29+
30+
if let Some(fill) = &self.attributes.fill {
31+
ts.extend(quote!( __efx_frame = __efx_frame.fill(#fill); ));
32+
}
33+
if let Some(im) = self.attributes.padding_ts() {
34+
ts.extend(quote!( __efx_frame = __efx_frame.inner_margin(#im); ));
35+
}
36+
if let Some(om) = self.attributes.margin_ts() {
37+
ts.extend(quote!( __efx_frame = __efx_frame.outer_margin(#om); ));
38+
}
39+
if let Some(st) = stroke_tokens(self.attributes.stroke_width, self.attributes.stroke_color.clone()) {
40+
ts.extend(quote!( __efx_frame = __efx_frame.stroke(#st); ));
41+
}
42+
43+
ts
44+
}
45+
46+
fn render<UI: ToTokens>(&self, ui: &UI) -> TokenStream {
47+
let id = match &self.attributes.id {
48+
Some(s) if !s.is_empty() => s,
49+
_ => {
50+
let msg = "efx: <BottomPanel> requires non-empty `id` attribute";
51+
return quote! { compile_error!(#msg); };
52+
}
53+
};
54+
55+
let children = render_children_stmt(&quote!(ui), &self.element.children);
56+
let frame_ts = self.content(ui);
57+
58+
let mut panel_ts = quote!( let mut __efx_panel = egui::TopBottomPanel::bottom(#id).frame(__efx_frame); );
59+
if let Some(b) = self.attributes.resizable {
60+
panel_ts.extend(quote!( __efx_panel = __efx_panel.resizable(#b); ));
61+
}
62+
if let Some(v) = self.attributes.default_height {
63+
panel_ts.extend(quote!( __efx_panel = __efx_panel.default_height(#v as f32); ));
64+
}
65+
if let Some(v) = self.attributes.min_height {
66+
panel_ts.extend(quote!( __efx_panel = __efx_panel.min_height(#v as f32); ));
67+
}
68+
if let Some(v) = self.attributes.max_height {
69+
panel_ts.extend(quote!( __efx_panel = __efx_panel.max_height(#v as f32); ));
70+
}
71+
72+
quote! {{
73+
#frame_ts
74+
#panel_ts
75+
__efx_panel.show(&#ui.ctx(), |ui| { #children });
76+
}}
77+
}
78+
}
79+
80+
#[derive(Clone, Debug, AttrNames)]
81+
struct Attributes {
82+
id: Option<String>,
83+
84+
frame: Option<bool>,
85+
fill: Option<TokenStream>,
86+
#[attr(name = "stroke-width")]
87+
stroke_width: Option<f32>,
88+
#[attr(name = "stroke-color")]
89+
stroke_color: Option<TokenStream>,
90+
91+
#[attr(name = "default-height")]
92+
default_height: Option<f32>,
93+
#[attr(name = "min-height")]
94+
min_height: Option<f32>,
95+
#[attr(name = "max-height")]
96+
max_height: Option<f32>,
97+
resizable: Option<bool>,
98+
99+
padding: Option<f32>,
100+
#[attr(name = "padding-left")]
101+
padding_l: Option<f32>,
102+
#[attr(name = "padding-right")]
103+
padding_r: Option<f32>,
104+
#[attr(name = "padding-top")]
105+
padding_t: Option<f32>,
106+
#[attr(name = "padding-bottom")]
107+
padding_b: Option<f32>,
108+
109+
margin: Option<f32>,
110+
#[attr(name = "margin-left")]
111+
margin_l: Option<f32>,
112+
#[attr(name = "margin-right")]
113+
margin_r: Option<f32>,
114+
#[attr(name = "margin-top")]
115+
margin_t: Option<f32>,
116+
#[attr(name = "margin-bottom")]
117+
margin_b: Option<f32>,
118+
}
119+
120+
impl Attributes {
121+
fn padding_ts(&self) -> Option<TokenStream> {
122+
margin_tokens(self.padding, self.padding_l, self.padding_r, self.padding_t, self.padding_b)
123+
}
124+
fn margin_ts(&self) -> Option<TokenStream> {
125+
margin_tokens(self.margin, self.margin_l, self.margin_r, self.margin_t, self.margin_b)
126+
}
127+
}
128+
129+
impl TagAttributes for Attributes {
130+
fn new(el: &Element) -> Result<Self, TokenStream> {
131+
let map = attr_map(el, Attributes::ATTR_NAMES, "BottomPanel")?;
132+
Ok(Attributes {
133+
id: map.get("id").map(|s| (*s).to_string()),
134+
frame: bool_opt(&map, "frame")?,
135+
fill: color_tokens_opt(&map, "fill")?,
136+
stroke_width: f32_opt(&map, "stroke-width")?,
137+
stroke_color: color_tokens_opt(&map, "stroke-color")?,
138+
default_height: f32_opt(&map, "default-height")?,
139+
min_height: f32_opt(&map, "min-height")?,
140+
max_height: f32_opt(&map, "max-height")?,
141+
resizable: bool_opt(&map, "resizable")?,
142+
padding: f32_opt(&map, "padding")?,
143+
padding_l: f32_opt(&map, "padding-left")?,
144+
padding_r: f32_opt(&map, "padding-right")?,
145+
padding_t: f32_opt(&map, "padding-top")?,
146+
padding_b: f32_opt(&map, "padding-bottom")?,
147+
margin: f32_opt(&map, "margin")?,
148+
margin_l: f32_opt(&map, "margin-left")?,
149+
margin_r: f32_opt(&map, "margin-right")?,
150+
margin_t: f32_opt(&map, "margin-top")?,
151+
margin_b: f32_opt(&map, "margin-bottom")?,
152+
})
153+
}
154+
}

efx/src/tags/mod.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,11 @@ pub mod label;
77
pub mod row;
88
pub mod scroll_area;
99
pub mod separator;
10+
pub mod side_panel;
1011
pub mod text_field;
12+
pub mod top_panel;
1113
pub mod window;
14+
pub mod bottom_panel;
1215

1316
pub use button::Button;
1417
pub use central_panel::CentralPanel;
@@ -22,7 +25,10 @@ use quote::{ToTokens, quote};
2225
pub use row::Row;
2326
pub use scroll_area::ScrollArea;
2427
pub use separator::Separator;
28+
pub use side_panel::SidePanel;
29+
pub use top_panel::TopPanel;
2530
pub use text_field::TextField;
31+
pub use bottom_panel::BottomPanel;
2632

2733
pub trait Tag: Sized {
2834
/// Constructor from Element (parses attributes and captures children inside self).

0 commit comments

Comments
 (0)