cursive_aligned_view/lib.rs
1//! # Align cursive views
2//!
3//! This crate provides an `AlignedView` for
4//! [gyscos/cursive](https://github.com/gyscos/cursive) views which makes it
5//! possible to align the child view (center, left, right, top, bottom). The
6//! `AlignedView` uses the `required_size` reported by the child view and fills
7//! the rest of the available space with the views background color.
8//!
9//! ## Aligning a child view
10//!
11//! The easiest way to align a view is via the `Alignable` trait:
12//!
13//! ```rust
14//! use cursive::{Cursive, CursiveExt};
15//! use cursive::view::Resizable;
16//! use cursive::views::{Panel, DummyView};
17//! use cursive_aligned_view::Alignable;
18//!
19//! fn main() {
20//! let mut siv = Cursive::default();
21//!
22//! let panel = Panel::new(DummyView)
23//! .title("Hello, world!")
24//! .fixed_width(20)
25//! .align_center();
26//!
27//! siv.add_fullscreen_layer(panel);
28//! // siv.run()
29//! }
30//! ```
31//!
32//! This is the preferred way as it is *chainable* and consistent with cursive's
33//! `Resizable` and `Identifiable` traits.
34//!
35//! As an alternative you can use the `AlignedView` constructors directly:
36//!
37//! ```rust
38//! use cursive::{Cursive, CursiveExt};
39//! use cursive::view::Resizable;
40//! use cursive::views::{Panel, DummyView};
41//! use cursive_aligned_view::AlignedView;
42//!
43//! fn main() {
44//! let mut siv = Cursive::default();
45//!
46//! let panel = Panel::new(DummyView)
47//! .title("Hello, world!")
48//! .fixed_width(20);
49//! let aligned = AlignedView::with_center(panel);
50//!
51//! siv.add_fullscreen_layer(aligned);
52//! // siv.run()
53//! }
54//! ```
55//!
56//! ## Supported Alignments
57//!
58//! | Alignment | Construction method |
59//! |---------------|-----------------------|
60//! | top left | `align_top_left` |
61//! | top center | `align_top_center` |
62//! | top right | `align_top_right` |
63//! | center left | `align_center_left` |
64//! | center | `align_center` |
65//! | center right | `align_center_right` |
66//! | bottom left | `align_bottom_left` |
67//! | bottom center | `align_bottom_center` |
68//! | bottom right | `align_bottom_right` |
69
70use cursive_core::align::{Align, HAlign, VAlign};
71use cursive_core::event::{Event, EventResult};
72use cursive_core::view::{View, ViewWrapper};
73use cursive_core::{Printer, Rect, Vec2};
74
75/// Use this trait to extend all `cursive::view::View` instances to support
76/// the `align_...` methods.
77///
78/// This trait provides *chainable* constructors for the `AlignedView`.
79///
80/// # Usage Example
81///
82/// ```rust
83/// use cursive::{Cursive, CursiveExt};
84/// use cursive::view::Resizable;
85/// use cursive::views::{Panel, DummyView};
86/// use cursive_aligned_view::Alignable;
87///
88/// fn main() {
89/// let mut siv = Cursive::default();
90///
91/// let panel = Panel::new(DummyView)
92/// .title("Hello, world!")
93/// .fixed_width(20)
94/// .align_top_center(); // constructing `AlignedView`
95///
96/// siv.add_fullscreen_layer(panel);
97/// // siv.run()
98/// }
99/// ```
100pub trait Alignable: View + Sized {
101 /// Align a child view at the top-left of the parent.
102 fn align_top_left(self) -> AlignedView<Self> {
103 AlignedView::with_top_left(self)
104 }
105
106 /// Align a child view at the top-center of the parent.
107 fn align_top_center(self) -> AlignedView<Self> {
108 AlignedView::with_top_center(self)
109 }
110
111 /// Align a child view at the top-right of the parent.
112 fn align_top_right(self) -> AlignedView<Self> {
113 AlignedView::with_top_right(self)
114 }
115
116 /// Align a child view at the center-left of the parent.
117 fn align_center_left(self) -> AlignedView<Self> {
118 AlignedView::with_center_left(self)
119 }
120
121 /// Align a child view at the center of the parent.
122 fn align_center(self) -> AlignedView<Self> {
123 AlignedView::with_center(self)
124 }
125
126 /// Align a child view at the center-right of the parent.
127 fn align_center_right(self) -> AlignedView<Self> {
128 AlignedView::with_center_right(self)
129 }
130
131 /// Align a child view at the bottom-left of the parent.
132 fn align_bottom_left(self) -> AlignedView<Self> {
133 AlignedView::with_bottom_left(self)
134 }
135
136 /// Align a child view at the bottom-center of the parent.
137 fn align_bottom_center(self) -> AlignedView<Self> {
138 AlignedView::with_bottom_center(self)
139 }
140
141 /// Align a child view at the bottom-right of the parent.
142 fn align_bottom_right(self) -> AlignedView<Self> {
143 AlignedView::with_bottom_right(self)
144 }
145}
146
147impl<T: View> Alignable for T {}
148
149/// This struct aligns a child view with a given alignment.
150///
151/// The child view will have its minimum allowed size. Additionally, the child
152/// may get cropped if it is larger than the available size.
153///
154/// The padded space around the child view is filled with the `View` color from
155/// cursive's color palette.
156///
157/// # Usage
158///
159/// The `AlignedView` may be used in 2 different ways:
160///
161/// 1. Via the `Alignable` composition trait
162/// 2. Via normal constructors
163///
164/// ## Using Alignable
165///
166/// ```rust
167/// use cursive::{Cursive, CursiveExt};
168/// use cursive::view::Resizable;
169/// use cursive::views::{Panel, DummyView};
170/// use cursive_aligned_view::Alignable;
171///
172/// fn main() {
173/// let mut siv = Cursive::default();
174///
175/// let panel = Panel::new(DummyView)
176/// .title("Hello, world!")
177/// .fixed_width(20)
178/// .align_top_center(); // `align_...` methods from `Alignable`
179///
180/// siv.add_fullscreen_layer(panel);
181/// // siv.run()
182/// }
183/// ```
184///
185/// ## Constructors
186///
187/// ```rust
188/// use cursive::{Cursive, CursiveExt};
189/// use cursive::view::Resizable;
190/// use cursive::views::{Panel, DummyView};
191/// use cursive_aligned_view::AlignedView;
192///
193/// fn main() {
194/// let mut siv = Cursive::default();
195///
196/// let panel = Panel::new(DummyView)
197/// .title("Hello, world!")
198/// .fixed_width(20);
199/// let aligned = AlignedView::with_bottom_center(panel); // constructor
200///
201/// siv.add_fullscreen_layer(aligned);
202/// // siv.run()
203/// }
204/// ```
205pub struct AlignedView<T> {
206 view: T,
207 alignment: Align,
208 last_size: Vec2,
209 offset: Vec2,
210 needs_relayout: bool,
211}
212
213impl<T: View> AlignedView<T> {
214 pub fn new(view: T, alignment: Align) -> Self {
215 Self {
216 view,
217 alignment,
218 last_size: Vec2::new(0, 0),
219 offset: Vec2::new(0, 0),
220 needs_relayout: false,
221 }
222 }
223
224 /// Wrap a child view and align it at the top-left of the parent.
225 pub fn with_top_left(view: T) -> Self {
226 Self::new(view, Align::new(HAlign::Left, VAlign::Top))
227 }
228
229 /// Wrap a child view and align it at the top-center of the parent.
230 pub fn with_top_center(view: T) -> Self {
231 Self::new(view, Align::new(HAlign::Center, VAlign::Top))
232 }
233
234 /// Wrap a child view and align it at the top-right of the parent.
235 pub fn with_top_right(view: T) -> Self {
236 Self::new(view, Align::new(HAlign::Right, VAlign::Top))
237 }
238
239 /// Wrap a child view and align it at the center-left of the parent.
240 pub fn with_center_left(view: T) -> Self {
241 Self::new(view, Align::new(HAlign::Left, VAlign::Center))
242 }
243
244 /// Wrap a child view and align it at the center of the parent.
245 pub fn with_center(view: T) -> Self {
246 Self::new(view, Align::new(HAlign::Center, VAlign::Center))
247 }
248
249 /// Wrap a child view and align it at the center-right of the parent.
250 pub fn with_center_right(view: T) -> Self {
251 Self::new(view, Align::new(HAlign::Right, VAlign::Center))
252 }
253
254 /// Wrap a child view and align it at the bottom-left of the parent.
255 pub fn with_bottom_left(view: T) -> Self {
256 Self::new(view, Align::new(HAlign::Left, VAlign::Bottom))
257 }
258
259 /// Wrap a child view and align it at the bottom-center of the parent.
260 pub fn with_bottom_center(view: T) -> Self {
261 Self::new(view, Align::new(HAlign::Center, VAlign::Bottom))
262 }
263
264 /// Wrap a child view and align it at the bottom-right of the parent.
265 pub fn with_bottom_right(view: T) -> Self {
266 Self::new(view, Align::new(HAlign::Right, VAlign::Bottom))
267 }
268
269 /// Set the alignment of this view to top-left.
270 pub fn set_top_left(&mut self) {
271 self.alignment = Align::new(HAlign::Left, VAlign::Top);
272 self.needs_relayout = true;
273 }
274
275 /// Set the alignment of this view to top-center.
276 pub fn set_top_center(&mut self) {
277 self.alignment = Align::new(HAlign::Center, VAlign::Top);
278 self.needs_relayout = true;
279 }
280
281 /// Set the alignment of this view to top-right.
282 pub fn set_top_right(&mut self) {
283 self.alignment = Align::new(HAlign::Right, VAlign::Top);
284 self.needs_relayout = true;
285 }
286
287 /// Set the alignment of this view to center-left.
288 pub fn set_center_left(&mut self) {
289 self.alignment = Align::new(HAlign::Left, VAlign::Center);
290 self.needs_relayout = true;
291 }
292
293 /// Set the alignment of this view to center.
294 pub fn set_center(&mut self) {
295 self.alignment = Align::new(HAlign::Center, VAlign::Center);
296 self.needs_relayout = true;
297 }
298
299 /// Set the alignment of this view to center-right.
300 pub fn set_center_right(&mut self) {
301 self.alignment = Align::new(HAlign::Right, VAlign::Center);
302 self.needs_relayout = true;
303 }
304
305 /// Set the alignment of this view to bottom-left.
306 pub fn set_bottom_left(&mut self) {
307 self.alignment = Align::new(HAlign::Left, VAlign::Bottom);
308 self.needs_relayout = true;
309 }
310
311 /// Set the alignment of this view to bottom-center.
312 pub fn set_bottom_center(&mut self) {
313 self.alignment = Align::new(HAlign::Center, VAlign::Bottom);
314 self.needs_relayout = true;
315 }
316
317 /// Set the alignment of this view to bottom-right.
318 pub fn set_bottom_right(&mut self) {
319 self.alignment = Align::new(HAlign::Right, VAlign::Bottom);
320 self.needs_relayout = true;
321 }
322
323 /// Get the current alignment of this view.
324 pub fn alignment(&self) -> &Align {
325 &self.alignment
326 }
327}
328
329impl<T: View> ViewWrapper for AlignedView<T> {
330 cursive_core::wrap_impl!(self.view: T);
331
332 fn wrap_draw(&self, printer: &Printer) {
333 let offset_printer = printer.offset(self.offset).cropped(self.last_size);
334 self.view.draw(&offset_printer);
335 }
336
337 fn wrap_layout(&mut self, size: Vec2) {
338 self.offset = Vec2::new(
339 self.alignment.h.get_offset(self.last_size.x, size.x),
340 self.alignment.v.get_offset(self.last_size.y, size.y),
341 );
342
343 let x = std::cmp::min(size.x, self.last_size.x);
344 let y = std::cmp::min(size.y, self.last_size.y);
345
346 self.view.layout(Vec2::new(x, y));
347
348 self.needs_relayout = false;
349 }
350
351 fn wrap_needs_relayout(&self) -> bool {
352 self.needs_relayout || self.view.needs_relayout()
353 }
354
355 fn wrap_required_size(&mut self, constraint: Vec2) -> Vec2 {
356 self.last_size = self.view.required_size(constraint);
357
358 self.last_size
359 }
360
361 fn wrap_on_event(&mut self, ev: Event) -> EventResult {
362 self.view.on_event(ev.relativized(self.offset))
363 }
364
365 fn wrap_important_area(&self, _: Vec2) -> Rect {
366 self.view.important_area(self.last_size) + self.offset
367 }
368}
369
370#[cursive_core::blueprint(AlignedView::new(view, alignment))]
371struct Blueprint {
372 view: cursive_core::views::BoxedView,
373 alignment: Align,
374}
375
376cursive_core::manual_blueprint!(with align, |config, context| {
377 let alignment = context.resolve(config)?;
378 Ok(move |view| AlignedView::new(view, alignment))
379});
380
381cursive_core::manual_blueprint!(with align_top_left, |_config, _context| {
382 Ok(|view| AlignedView::with_top_left(view))
383});
384
385cursive_core::manual_blueprint!(with align_top_center, |_config, _context| {
386 Ok(|view| AlignedView::with_top_center(view))
387});
388
389cursive_core::manual_blueprint!(with align_top_right, |_config, _context| {
390 Ok(|view| AlignedView::with_top_right(view))
391});
392
393cursive_core::manual_blueprint!(with align_center_left, |_config, _context| {
394 Ok(|view| AlignedView::with_center_left(view))
395});
396
397cursive_core::manual_blueprint!(with align_center, |_config, _context| {
398 Ok(|view| AlignedView::with_center(view))
399});
400
401cursive_core::manual_blueprint!(with align_center_right, |_config, _context| {
402 Ok(|view| AlignedView::with_center_right(view))
403});
404
405cursive_core::manual_blueprint!(with align_bottom_left, |_config, _context| {
406 Ok(|view| AlignedView::with_bottom_left(view))
407});
408
409cursive_core::manual_blueprint!(with align_bottom_center, |_config, _context| {
410 Ok(|view| AlignedView::with_bottom_center(view))
411});
412
413cursive_core::manual_blueprint!(with align_bottom_right, |_config, _context| {
414 Ok(|view| AlignedView::with_bottom_right(view))
415});