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});