cursive_core/views/
panel.rs1use crate::align::*;
2use crate::event::{Event, EventResult};
3use crate::rect::Rect;
4use crate::style::PaletteStyle;
5use crate::utils::markup::StyledString;
6use crate::view::{View, ViewWrapper};
7use crate::Printer;
8use crate::Vec2;
9use crate::With;
10
11#[derive(Debug)]
13pub struct Panel<V> {
14 view: V,
16
17 title: StyledString,
19
20 title_position: HAlign,
22
23 invalidated: bool,
25}
26
27new_default!(Panel<V: Default>);
28
29const TITLE_SPACING: usize = 3;
31
32impl<V> Panel<V> {
33 pub fn new(view: V) -> Self {
35 Panel {
36 view,
37 title: StyledString::new(),
38 title_position: HAlign::Center,
39 invalidated: true,
40 }
41 }
42
43 #[must_use]
47 pub fn title<S: Into<StyledString>>(self, label: S) -> Self {
48 self.with(|s| s.set_title(label))
49 }
50
51 pub fn set_title<S: Into<StyledString>>(&mut self, label: S) {
53 self.title = label.into();
54 self.invalidate();
55 }
56
57 #[must_use]
60 pub fn title_position(self, align: HAlign) -> Self {
61 self.with(|s| s.set_title_position(align))
62 }
63
64 pub fn set_title_position(&mut self, align: HAlign) {
67 self.title_position = align;
68 }
69
70 fn draw_title(&self, printer: &Printer) {
71 if !self.title.is_empty() {
72 let available = match printer.size.x.checked_sub(2 * TITLE_SPACING) {
73 Some(available) => available,
74 None => return, };
76 let len = std::cmp::min(self.title.width(), available);
77 let x = TITLE_SPACING + self.title_position.get_offset(len, available);
78
79 printer
80 .offset((x, 0))
81 .cropped((len, 1))
82 .with_style(PaletteStyle::TitlePrimary, |p| {
83 p.print_styled((0, 0), &self.title)
84 });
85 printer.with_high_border(false, |printer| {
86 printer.print((x - 2, 0), "┤ ");
87 printer.print((x + len, 0), " ├");
88 });
89 }
90 }
91
92 fn invalidate(&mut self) {
93 self.invalidated = true;
94 }
95
96 inner_getters!(self.view: V);
97}
98
99impl<V: View> ViewWrapper for Panel<V> {
100 wrap_impl!(self.view: V);
101
102 fn wrap_on_event(&mut self, event: Event) -> EventResult {
103 self.view.on_event(event.relativized((1, 1)))
104 }
105
106 fn wrap_required_size(&mut self, req: Vec2) -> Vec2 {
107 let req = req.saturating_sub((2, 2));
109
110 let size = self.view.required_size(req) + (2, 2);
111 if self.title.is_empty() {
112 size
113 } else {
114 let title_width = self.title.width() + 2 * TITLE_SPACING;
115 size.or_max((title_width, 0))
116 }
117 }
118
119 fn wrap_draw(&self, printer: &Printer) {
120 printer.print_box((0, 0), printer.size, true);
121 self.draw_title(printer);
122
123 let printer = printer.offset((1, 1)).shrinked((1, 1));
124 self.view.draw(&printer);
125 }
126
127 fn wrap_layout(&mut self, size: Vec2) {
128 self.view.layout(size.saturating_sub((2, 2)));
129 }
130
131 fn wrap_important_area(&self, size: Vec2) -> Rect {
132 let inner_size = size.saturating_sub((2, 2));
133 self.view.important_area(inner_size) + (1, 1)
134 }
135
136 fn wrap_needs_relayout(&self) -> bool {
137 self.invalidated || self.view.needs_relayout()
138 }
139}
140
141#[crate::blueprint(Panel::new(view))]
142struct Blueprint {
143 view: crate::views::BoxedView,
144
145 title: Option<StyledString>,
146 title_position: Option<HAlign>,
147}
148
149crate::manual_blueprint!(with panel, |config, context| {
151 let title = match config {
152 crate::builder::Config::String(_) => context.resolve(config)?,
153 crate::builder::Config::Object(config) => {
154 match config.get("title") {
155 Some(title) => context.resolve(title)?,
156 None => StyledString::new()
157 }
158 }
159 _ => StyledString::new(),
160 };
161
162 let title_position = context.resolve(&config["title_position"])?;
163
164 Ok(move |view| {
165 let mut panel = crate::views::Panel::new(view).title(title);
166
167 if let Some(title_position) = title_position {
168 panel.set_title_position(title_position);
169 }
170
171 panel
172 })
173});