mirror of
https://github.com/FliegendeWurst/cursive.git
synced 2024-11-27 11:16:03 +00:00
More safe subtractions all around.
This commit is contained in:
parent
14161f51e4
commit
05bac7a192
@ -167,6 +167,7 @@ impl<'a> Printer<'a> {
|
|||||||
|
|
||||||
let start = start.into();
|
let start = start.into();
|
||||||
let size = size.into();
|
let size = size.into();
|
||||||
|
|
||||||
if size.x < 2 || size.y < 2 {
|
if size.x < 2 || size.y < 2 {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -247,8 +248,8 @@ impl<'a> Printer<'a> {
|
|||||||
pub fn print_hdelim<T: Into<Vec2>>(&self, start: T, len: usize) {
|
pub fn print_hdelim<T: Into<Vec2>>(&self, start: T, len: usize) {
|
||||||
let start = start.into();
|
let start = start.into();
|
||||||
self.print(start, "├");
|
self.print(start, "├");
|
||||||
self.print_hline(start + (1, 0), len - 2, "─");
|
self.print_hline(start + (1, 0), len.saturating_sub(2), "─");
|
||||||
self.print(start + (len - 1, 0), "┤");
|
self.print(start + (len.saturating_sub(1), 0), "┤");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a printer on a subset of this one's area.
|
/// Returns a printer on a subset of this one's area.
|
||||||
@ -257,10 +258,15 @@ impl<'a> Printer<'a> {
|
|||||||
-> Printer<'a> {
|
-> Printer<'a> {
|
||||||
let size = size.into();
|
let size = size.into();
|
||||||
let offset = offset.into().or_min(self.size);
|
let offset = offset.into().or_min(self.size);
|
||||||
|
let available = if !offset.fits_in(self.size) {
|
||||||
|
Vec2::zero()
|
||||||
|
} else {
|
||||||
|
Vec2::min(self.size - offset, size)
|
||||||
|
};
|
||||||
Printer {
|
Printer {
|
||||||
offset: self.offset + offset,
|
offset: self.offset + offset,
|
||||||
// We can't be larger than what remains
|
// We can't be larger than what remains
|
||||||
size: Vec2::min(self.size - offset, size),
|
size: available,
|
||||||
focused: self.focused && focused,
|
focused: self.focused && focused,
|
||||||
theme: self.theme,
|
theme: self.theme,
|
||||||
backend: self.backend,
|
backend: self.backend,
|
||||||
|
42
src/vec.rs
42
src/vec.rs
@ -30,15 +30,57 @@ impl PartialOrd for XY<usize> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl XY<usize> {
|
||||||
|
/// Saturating subtraction. Computes `self - other`, saturating at 0.
|
||||||
|
///
|
||||||
|
/// Never panics.
|
||||||
|
pub fn saturating_sub<O: Into<Self>>(&self, other: O) -> Self {
|
||||||
|
let other = other.into();
|
||||||
|
Self::new(self.x.saturating_sub(other.x),
|
||||||
|
self.y.saturating_sub(other.y))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checked subtraction. Computes `self - other` if possible.
|
||||||
|
///
|
||||||
|
/// Returns `None` if `self.x < other.x || self.y < other.y`.
|
||||||
|
///
|
||||||
|
/// Never panics.
|
||||||
|
pub fn checked_sub<O: Into<Self>>(&self, other: O) -> Option<Self> {
|
||||||
|
let other = other.into();
|
||||||
|
if self.fits(other) {
|
||||||
|
Some(*self - other)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a `XY<isize>` from `self`.
|
||||||
|
pub fn signed(self) -> XY<isize> {
|
||||||
|
self.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<T: Ord> XY<T> {
|
impl<T: Ord> XY<T> {
|
||||||
/// Returns `true` if `self` could fit inside `other`.
|
/// Returns `true` if `self` could fit inside `other`.
|
||||||
///
|
///
|
||||||
/// Shortcut for `self.x <= other.x && self.y <= other.y`.
|
/// Shortcut for `self.x <= other.x && self.y <= other.y`.
|
||||||
|
///
|
||||||
|
/// If this returns `true`, then `other - self` will not underflow.
|
||||||
pub fn fits_in<O: Into<Self>>(&self, other: O) -> bool {
|
pub fn fits_in<O: Into<Self>>(&self, other: O) -> bool {
|
||||||
let other = other.into();
|
let other = other.into();
|
||||||
self.x <= other.x && self.y <= other.y
|
self.x <= other.x && self.y <= other.y
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if `other` could fit inside `self`.
|
||||||
|
///
|
||||||
|
/// Shortcut for `self.x >= other.x && self.y >= other.y`.
|
||||||
|
///
|
||||||
|
/// If this returns `true`, then `self - other` will not underflow.
|
||||||
|
pub fn fits<O: Into<Self>>(&self, other: O) -> bool {
|
||||||
|
let other = other.into();
|
||||||
|
self.x >= other.x && self.y >= other.y
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns a new Vec2 that is a maximum per coordinate.
|
/// Returns a new Vec2 that is a maximum per coordinate.
|
||||||
pub fn max<A: Into<XY<T>>, B: Into<XY<T>>>(a: A, b: B) -> Self {
|
pub fn max<A: Into<XY<T>>, B: Into<XY<T>>>(a: A, b: B) -> Self {
|
||||||
let a = a.into();
|
let a = a.into();
|
||||||
|
@ -177,14 +177,12 @@ impl ScrollBase {
|
|||||||
let max_y = min(self.view_height,
|
let max_y = min(self.view_height,
|
||||||
self.content_height - self.start_line);
|
self.content_height - self.start_line);
|
||||||
let w = if self.scrollable() {
|
let w = if self.scrollable() {
|
||||||
if printer.size.x < 2 {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// We have to remove the bar width and the padding.
|
// We have to remove the bar width and the padding.
|
||||||
printer.size.x - 1 - self.right_padding
|
printer.size.x.saturating_sub(1 + self.right_padding)
|
||||||
} else {
|
} else {
|
||||||
printer.size.x
|
printer.size.x
|
||||||
};
|
};
|
||||||
|
|
||||||
for y in 0..max_y {
|
for y in 0..max_y {
|
||||||
// Y is the actual coordinate of the line.
|
// Y is the actual coordinate of the line.
|
||||||
// The item ID is then Y + self.start_line
|
// The item ID is then Y + self.start_line
|
||||||
@ -218,7 +216,7 @@ impl ScrollBase {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// TODO: use 1 instead of 2
|
// TODO: use 1 instead of 2
|
||||||
let scrollbar_x = printer.size.x - 1 - self.scrollbar_offset;
|
let scrollbar_x = printer.size.x.saturating_sub(1 + self.scrollbar_offset);
|
||||||
printer.print_vline((scrollbar_x, 0), printer.size.y, "|");
|
printer.print_vline((scrollbar_x, 0), printer.size.y, "|");
|
||||||
printer.with_color(color, |printer| {
|
printer.with_color(color, |printer| {
|
||||||
printer.print_vline((scrollbar_x, start), height, "▒");
|
printer.print_vline((scrollbar_x, start), height, "▒");
|
||||||
|
@ -179,28 +179,26 @@ impl View for Dialog {
|
|||||||
// Current horizontal position of the next button we'll draw.
|
// Current horizontal position of the next button we'll draw.
|
||||||
|
|
||||||
// Sum of the sizes + len-1 for margins
|
// Sum of the sizes + len-1 for margins
|
||||||
let width = if self.buttons.is_empty() {
|
let width = self.buttons
|
||||||
0
|
.iter()
|
||||||
} else {
|
.map(|button| button.size.x)
|
||||||
self.buttons
|
.fold(0, |a, b| a + b) +
|
||||||
.iter()
|
self.buttons.len().saturating_sub(1);
|
||||||
.map(|button| button.size.x)
|
|
||||||
.fold(0, |a, b| a + b) + self.buttons.len() - 1
|
|
||||||
};
|
|
||||||
let overhead = self.padding + self.borders;
|
let overhead = self.padding + self.borders;
|
||||||
if printer.size.x < overhead.horizontal() {
|
if printer.size.x < overhead.horizontal() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let mut offset = overhead.left +
|
let mut offset = overhead.left +
|
||||||
self.align
|
self.align.h.get_offset(width,
|
||||||
.h
|
printer.size.x -
|
||||||
.get_offset(width, printer.size.x - overhead.horizontal());
|
overhead.horizontal());
|
||||||
|
|
||||||
let overhead_bottom = self.padding.bottom + self.borders.bottom + 1;
|
let overhead_bottom = self.padding.bottom + self.borders.bottom + 1;
|
||||||
if overhead_bottom > printer.size.y {
|
|
||||||
return;
|
let y = match printer.size.y.checked_sub(overhead_bottom) {
|
||||||
}
|
Some(y) => y,
|
||||||
let y = printer.size.y - overhead_bottom;
|
None => return,
|
||||||
|
};
|
||||||
|
|
||||||
for (i, button) in self.buttons.iter().enumerate() {
|
for (i, button) in self.buttons.iter().enumerate() {
|
||||||
let size = button.size;
|
let size = button.size;
|
||||||
@ -217,16 +215,16 @@ impl View for Dialog {
|
|||||||
// What do we have left?
|
// What do we have left?
|
||||||
let taken = Vec2::new(0, buttons_height) + self.borders.combined() +
|
let taken = Vec2::new(0, buttons_height) + self.borders.combined() +
|
||||||
self.padding.combined();
|
self.padding.combined();
|
||||||
if !taken.fits_in(printer.size) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let inner_size = printer.size - taken;
|
|
||||||
|
|
||||||
self.content
|
let inner_size = match printer.size.checked_sub(taken) {
|
||||||
.draw(&printer.sub_printer(self.borders.top_left() +
|
Some(s) => s,
|
||||||
self.padding.top_left(),
|
None => return,
|
||||||
inner_size,
|
};
|
||||||
self.focus == Focus::Content));
|
|
||||||
|
self.content.draw(&printer.sub_printer(self.borders.top_left() +
|
||||||
|
self.padding.top_left(),
|
||||||
|
inner_size,
|
||||||
|
self.focus == Focus::Content));
|
||||||
|
|
||||||
printer.print_box(Vec2::new(0, 0), printer.size, false);
|
printer.print_box(Vec2::new(0, 0), printer.size, false);
|
||||||
|
|
||||||
@ -253,9 +251,10 @@ impl View for Dialog {
|
|||||||
|
|
||||||
// Buttons are not flexible, so their size doesn't depend on ours.
|
// Buttons are not flexible, so their size doesn't depend on ours.
|
||||||
let mut buttons_size = Vec2::new(0, 0);
|
let mut buttons_size = Vec2::new(0, 0);
|
||||||
if !self.buttons.is_empty() {
|
|
||||||
buttons_size.x += self.buttons.len() - 1;
|
// Start with the inter-button space.
|
||||||
}
|
buttons_size.x += self.buttons.len().saturating_sub(1);
|
||||||
|
|
||||||
for button in &mut self.buttons {
|
for button in &mut self.buttons {
|
||||||
let s = button.view.required_size(req);
|
let s = button.view.required_size(req);
|
||||||
buttons_size.x += s.x;
|
buttons_size.x += s.x;
|
||||||
@ -264,11 +263,12 @@ impl View for Dialog {
|
|||||||
|
|
||||||
// We also remove one row for the buttons.
|
// We also remove one row for the buttons.
|
||||||
let taken = nomans_land + Vec2::new(0, buttons_size.y);
|
let taken = nomans_land + Vec2::new(0, buttons_size.y);
|
||||||
if !taken.fits_in(req) {
|
|
||||||
|
let content_req = match req.checked_sub(taken) {
|
||||||
|
Some(r) => r,
|
||||||
// Bad!!
|
// Bad!!
|
||||||
return taken;
|
None => return taken,
|
||||||
}
|
};
|
||||||
let content_req = req - taken;
|
|
||||||
|
|
||||||
let content_size = self.content.required_size(content_req);
|
let content_size = self.content.required_size(content_req);
|
||||||
|
|
||||||
@ -291,11 +291,7 @@ impl View for Dialog {
|
|||||||
// Padding and borders are taken, sorry.
|
// Padding and borders are taken, sorry.
|
||||||
// TODO: handle border-less themes?
|
// TODO: handle border-less themes?
|
||||||
let taken = self.borders.combined() + self.padding.combined();
|
let taken = self.borders.combined() + self.padding.combined();
|
||||||
size = if taken.fits_in(size) {
|
size = size.saturating_sub(taken);
|
||||||
size - taken
|
|
||||||
} else {
|
|
||||||
Vec2::zero()
|
|
||||||
};
|
|
||||||
|
|
||||||
// Buttons are kings, we give them everything they want.
|
// Buttons are kings, we give them everything they want.
|
||||||
let mut buttons_height = 0;
|
let mut buttons_height = 0;
|
||||||
@ -309,7 +305,7 @@ impl View for Dialog {
|
|||||||
if buttons_height > size.y {
|
if buttons_height > size.y {
|
||||||
buttons_height = size.y;
|
buttons_height = size.y;
|
||||||
}
|
}
|
||||||
self.content.layout(size - Vec2::new(0, buttons_height));
|
self.content.layout(size.saturating_sub((0, buttons_height)));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_event(&mut self, event: Event) -> EventResult {
|
fn on_event(&mut self, event: Event) -> EventResult {
|
||||||
@ -339,8 +335,7 @@ impl View for Dialog {
|
|||||||
match event {
|
match event {
|
||||||
// Up goes back to the content
|
// Up goes back to the content
|
||||||
Event::Key(Key::Up) => {
|
Event::Key(Key::Up) => {
|
||||||
if self.content
|
if self.content.take_focus(Direction::down()) {
|
||||||
.take_focus(Direction::down()) {
|
|
||||||
self.focus = Focus::Content;
|
self.focus = Focus::Content;
|
||||||
EventResult::Consumed(None)
|
EventResult::Consumed(None)
|
||||||
} else {
|
} else {
|
||||||
@ -348,8 +343,7 @@ impl View for Dialog {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Event::Shift(Key::Tab) => {
|
Event::Shift(Key::Tab) => {
|
||||||
if self.content
|
if self.content.take_focus(Direction::back()) {
|
||||||
.take_focus(Direction::back()) {
|
|
||||||
self.focus = Focus::Content;
|
self.focus = Focus::Content;
|
||||||
EventResult::Consumed(None)
|
EventResult::Consumed(None)
|
||||||
} else {
|
} else {
|
||||||
@ -358,7 +352,7 @@ impl View for Dialog {
|
|||||||
}
|
}
|
||||||
Event::Key(Key::Tab) => {
|
Event::Key(Key::Tab) => {
|
||||||
if self.content
|
if self.content
|
||||||
.take_focus(Direction::front()) {
|
.take_focus(Direction::front()) {
|
||||||
self.focus = Focus::Content;
|
self.focus = Focus::Content;
|
||||||
EventResult::Consumed(None)
|
EventResult::Consumed(None)
|
||||||
} else {
|
} else {
|
||||||
@ -367,8 +361,7 @@ impl View for Dialog {
|
|||||||
}
|
}
|
||||||
// Left and Right move to other buttons
|
// Left and Right move to other buttons
|
||||||
Event::Key(Key::Right) if i + 1 <
|
Event::Key(Key::Right) if i + 1 <
|
||||||
self.buttons
|
self.buttons.len() => {
|
||||||
.len() => {
|
|
||||||
self.focus = Focus::Button(i + 1);
|
self.focus = Focus::Button(i + 1);
|
||||||
EventResult::Consumed(None)
|
EventResult::Consumed(None)
|
||||||
}
|
}
|
||||||
@ -400,7 +393,7 @@ impl View for Dialog {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn call_on_any<'a>(&mut self, selector: &Selector,
|
fn call_on_any<'a>(&mut self, selector: &Selector,
|
||||||
callback: Box<FnMut(&mut Any) + 'a>) {
|
callback: Box<FnMut(&mut Any) + 'a>) {
|
||||||
self.content.call_on_any(selector, callback);
|
self.content.call_on_any(selector, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -370,13 +370,14 @@ impl EditView {
|
|||||||
.map(|g| g.width())
|
.map(|g| g.width())
|
||||||
.next()
|
.next()
|
||||||
.unwrap_or(1);
|
.unwrap_or(1);
|
||||||
if c_len > self.last_length {
|
|
||||||
// Weird - no available space?
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Now, we have to fit self.content[..self.cursor]
|
// Now, we have to fit self.content[..self.cursor]
|
||||||
// into self.last_length - c_len.
|
// into self.last_length - c_len.
|
||||||
let available = self.last_length - c_len;
|
let available = match self.last_length.checked_sub(c_len) {
|
||||||
|
Some(s) => s,
|
||||||
|
// Weird - no available space?
|
||||||
|
None => return,
|
||||||
|
};
|
||||||
// Look at the content before the cursor (we will print its tail).
|
// Look at the content before the cursor (we will print its tail).
|
||||||
// From the end, count the length until we reach `available`.
|
// From the end, count the length until we reach `available`.
|
||||||
// Then sum the byte lengths.
|
// Then sum the byte lengths.
|
||||||
@ -384,6 +385,8 @@ impl EditView {
|
|||||||
self.cursor],
|
self.cursor],
|
||||||
available)
|
available)
|
||||||
.length;
|
.length;
|
||||||
|
|
||||||
|
assert!(suffix_length <= self.cursor);
|
||||||
self.offset = self.cursor - suffix_length;
|
self.offset = self.cursor - suffix_length;
|
||||||
// Make sure the cursor is in view
|
// Make sure the cursor is in view
|
||||||
assert!(self.cursor >= self.offset);
|
assert!(self.cursor >= self.offset);
|
||||||
@ -392,8 +395,11 @@ impl EditView {
|
|||||||
|
|
||||||
// If we have too much space
|
// If we have too much space
|
||||||
if self.content[self.offset..].width() < self.last_length {
|
if self.content[self.offset..].width() < self.last_length {
|
||||||
|
assert!(self.last_length >= 1);
|
||||||
let suffix_length =
|
let suffix_length =
|
||||||
simple_suffix(&self.content, self.last_length - 1).length;
|
simple_suffix(&self.content, self.last_length - 1).length;
|
||||||
|
|
||||||
|
assert!(self.content.len() >= 1);
|
||||||
self.offset = self.content.len() - suffix_length;
|
self.offset = self.content.len() - suffix_length;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -425,6 +431,7 @@ impl View for EditView {
|
|||||||
printer.with_effect(effect, |printer| {
|
printer.with_effect(effect, |printer| {
|
||||||
if width < self.last_length {
|
if width < self.last_length {
|
||||||
// No problem, everything fits.
|
// No problem, everything fits.
|
||||||
|
assert!(printer.size.x >= width);
|
||||||
if self.secret {
|
if self.secret {
|
||||||
printer.print_hline((0, 0), width, "*");
|
printer.print_hline((0, 0), width, "*");
|
||||||
} else {
|
} else {
|
||||||
|
@ -270,7 +270,7 @@ impl View for LinearLayout {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// This here is how much we're generously offered
|
// This here is how much we're generously offered
|
||||||
// (We just checked that req >= desperate, so the substraction is safe
|
// (We just checked that req >= desperate, so the subtraction is safe
|
||||||
let mut available = self.orientation.get(&(req - desperate));
|
let mut available = self.orientation.get(&(req - desperate));
|
||||||
// println_stderr!("Available: {:?}", available);
|
// println_stderr!("Available: {:?}", available);
|
||||||
|
|
||||||
@ -279,7 +279,7 @@ impl View for LinearLayout {
|
|||||||
let mut overweight: Vec<(usize, usize)> = sizes.iter()
|
let mut overweight: Vec<(usize, usize)> = sizes.iter()
|
||||||
.map(|v| self.orientation.get(v))
|
.map(|v| self.orientation.get(v))
|
||||||
.zip(min_sizes.iter().map(|v| self.orientation.get(v)))
|
.zip(min_sizes.iter().map(|v| self.orientation.get(v)))
|
||||||
.map(|(a, b)| if a > b { a - b } else { 0 })
|
.map(|(a, b)| a.saturating_sub(b))
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.collect();
|
.collect();
|
||||||
// println_stderr!("Overweight: {:?}", overweight);
|
// println_stderr!("Overweight: {:?}", overweight);
|
||||||
|
@ -199,7 +199,8 @@ impl ListView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn try_focus((i, child): (usize, &mut ListChild), source: direction::Direction)
|
fn try_focus((i, child): (usize, &mut ListChild),
|
||||||
|
source: direction::Direction)
|
||||||
-> Option<usize> {
|
-> Option<usize> {
|
||||||
match *child {
|
match *child {
|
||||||
ListChild::Delimiter => None,
|
ListChild::Delimiter => None,
|
||||||
@ -274,12 +275,8 @@ impl View for ListView {
|
|||||||
let spacing = 1;
|
let spacing = 1;
|
||||||
let scrollbar_width = if self.children.len() > size.y { 2 } else { 0 };
|
let scrollbar_width = if self.children.len() > size.y { 2 } else { 0 };
|
||||||
|
|
||||||
let available = if label_width + spacing + scrollbar_width > size.x {
|
let available = size.x.saturating_sub(label_width + spacing +
|
||||||
// We have no space for the kids! :(
|
scrollbar_width);
|
||||||
0
|
|
||||||
} else {
|
|
||||||
size.x - label_width - spacing - scrollbar_width
|
|
||||||
};
|
|
||||||
|
|
||||||
// println_stderr!("Available: {}", available);
|
// println_stderr!("Available: {}", available);
|
||||||
|
|
||||||
@ -351,10 +348,8 @@ impl View for ListView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn call_on_any<'a>(&mut self, selector: &Selector,
|
fn call_on_any<'a>(&mut self, selector: &Selector,
|
||||||
mut callback: Box<FnMut(&mut Any) + 'a>) {
|
mut callback: Box<FnMut(&mut Any) + 'a>) {
|
||||||
for view in self.children
|
for view in self.children.iter_mut().filter_map(ListChild::view) {
|
||||||
.iter_mut()
|
|
||||||
.filter_map(ListChild::view) {
|
|
||||||
view.call_on_any(selector, Box::new(|any| callback(any)));
|
view.call_on_any(selector, Box::new(|any| callback(any)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -364,7 +359,9 @@ impl View for ListView {
|
|||||||
.iter_mut()
|
.iter_mut()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.filter_map(|(i, v)| v.view().map(|v| (i, v)))
|
.filter_map(|(i, v)| v.view().map(|v| (i, v)))
|
||||||
.filter_map(|(i, v)| v.focus_view(selector).ok().map(|_| i))
|
.filter_map(|(i, v)| {
|
||||||
|
v.focus_view(selector).ok().map(|_| i)
|
||||||
|
})
|
||||||
.next() {
|
.next() {
|
||||||
self.focus = i;
|
self.focus = i;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -170,7 +170,7 @@ impl MenuPopup {
|
|||||||
|
|
||||||
impl View for MenuPopup {
|
impl View for MenuPopup {
|
||||||
fn draw(&self, printer: &Printer) {
|
fn draw(&self, printer: &Printer) {
|
||||||
if printer.size.x < 2 || printer.size.y < 2 {
|
if !printer.size.fits((2, 2)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -200,7 +200,8 @@ impl View for MenuPopup {
|
|||||||
}
|
}
|
||||||
printer.print_hline((1, 0), printer.size.x - 2, " ");
|
printer.print_hline((1, 0), printer.size.x - 2, " ");
|
||||||
printer.print((2, 0), label);
|
printer.print((2, 0), label);
|
||||||
printer.print((printer.size.x - 4, 0), ">>");
|
printer.print((printer.size.x.saturating_sub(4), 0),
|
||||||
|
">>");
|
||||||
}
|
}
|
||||||
MenuItem::Leaf(ref label, _) => {
|
MenuItem::Leaf(ref label, _) => {
|
||||||
if printer.size.x < 2 {
|
if printer.size.x < 2 {
|
||||||
@ -251,7 +252,12 @@ impl View for MenuPopup {
|
|||||||
Event::Key(Key::PageDown) => self.scroll_down(5, false),
|
Event::Key(Key::PageDown) => self.scroll_down(5, false),
|
||||||
|
|
||||||
Event::Key(Key::Home) => self.focus = 0,
|
Event::Key(Key::Home) => self.focus = 0,
|
||||||
Event::Key(Key::End) => self.focus = self.menu.children.len() - 1,
|
Event::Key(Key::End) => {
|
||||||
|
self.focus = self.menu
|
||||||
|
.children
|
||||||
|
.len()
|
||||||
|
.saturating_sub(1)
|
||||||
|
}
|
||||||
|
|
||||||
Event::Key(Key::Right) if self.menu.children[self.focus]
|
Event::Key(Key::Right) if self.menu.children[self.focus]
|
||||||
.is_subtree() => {
|
.is_subtree() => {
|
||||||
@ -297,6 +303,7 @@ impl View for MenuPopup {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn layout(&mut self, size: Vec2) {
|
fn layout(&mut self, size: Vec2) {
|
||||||
self.scrollbase.set_heights(size.y - 2, self.menu.children.len());
|
self.scrollbase.set_heights(size.y.saturating_sub(2),
|
||||||
|
self.menu.children.len());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,11 +20,7 @@ impl<V: View> ViewWrapper for Panel<V> {
|
|||||||
|
|
||||||
fn wrap_required_size(&mut self, req: Vec2) -> Vec2 {
|
fn wrap_required_size(&mut self, req: Vec2) -> Vec2 {
|
||||||
// TODO: make borders conditional?
|
// TODO: make borders conditional?
|
||||||
let req = if Vec2::new(2, 2).fits_in(req) {
|
let req = req.saturating_sub((2, 2));
|
||||||
req - (2, 2)
|
|
||||||
} else {
|
|
||||||
Vec2::zero()
|
|
||||||
};
|
|
||||||
|
|
||||||
self.view.required_size(req) + (2, 2)
|
self.view.required_size(req) + (2, 2)
|
||||||
}
|
}
|
||||||
@ -32,15 +28,11 @@ impl<V: View> ViewWrapper for Panel<V> {
|
|||||||
fn wrap_draw(&self, printer: &Printer) {
|
fn wrap_draw(&self, printer: &Printer) {
|
||||||
printer.print_box((0, 0), printer.size, true);
|
printer.print_box((0, 0), printer.size, true);
|
||||||
self.view.draw(&printer.sub_printer((1, 1),
|
self.view.draw(&printer.sub_printer((1, 1),
|
||||||
printer.size - (2, 2),
|
printer.size.saturating_sub((2, 2)),
|
||||||
true));
|
true));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn wrap_layout(&mut self, size: Vec2) {
|
fn wrap_layout(&mut self, size: Vec2) {
|
||||||
self.view.layout(if Vec2::new(2, 2).fits_in(size) {
|
self.view.layout(size.saturating_sub((2, 2)));
|
||||||
size - (2, 2)
|
|
||||||
} else {
|
|
||||||
size
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
use Cursive;
|
use Cursive;
|
||||||
use Printer;
|
use Printer;
|
||||||
use With;
|
use With;
|
||||||
use XY;
|
|
||||||
use align::{Align, HAlign, VAlign};
|
use align::{Align, HAlign, VAlign};
|
||||||
use direction::Direction;
|
use direction::Direction;
|
||||||
use event::{Callback, Event, EventResult, Key};
|
use event::{Callback, Event, EventResult, Key};
|
||||||
@ -67,7 +66,7 @@ pub struct SelectView<T = String> {
|
|||||||
last_size: Vec2,
|
last_size: Vec2,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl <T: 'static> Default for SelectView<T> {
|
impl<T: 'static> Default for SelectView<T> {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self::new()
|
Self::new()
|
||||||
}
|
}
|
||||||
@ -252,7 +251,8 @@ impl<T: 'static> SelectView<T> {
|
|||||||
printer.print_hline((0, 0), x, " ");
|
printer.print_hline((0, 0), x, " ");
|
||||||
printer.print((x, 0), &self.items[i].label);
|
printer.print((x, 0), &self.items[i].label);
|
||||||
if l < printer.size.x {
|
if l < printer.size.x {
|
||||||
printer.print_hline((x + l, 0), printer.size.x - l - x, " ");
|
assert!((l + x) <= printer.size.x);
|
||||||
|
printer.print_hline((x + l, 0), printer.size.x - (l + x), " ");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -290,12 +290,11 @@ impl<T: 'static> SelectView<T> {
|
|||||||
|
|
||||||
fn focus_up(&mut self, n: usize) {
|
fn focus_up(&mut self, n: usize) {
|
||||||
let focus = self.focus();
|
let focus = self.focus();
|
||||||
let n = min(focus, n);
|
self.focus.set(focus.saturating_sub(n));
|
||||||
self.focus.set(focus - n);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn focus_down(&mut self, n: usize) {
|
fn focus_down(&mut self, n: usize) {
|
||||||
let focus = min(self.focus() + n, self.items.len() - 1);
|
let focus = min(self.focus() + n, self.items.len().saturating_sub(1));
|
||||||
self.focus.set(focus);
|
self.focus.set(focus);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -353,23 +352,22 @@ impl<T: 'static> View for SelectView<T> {
|
|||||||
} else {
|
} else {
|
||||||
ColorStyle::Highlight
|
ColorStyle::Highlight
|
||||||
};
|
};
|
||||||
let x = printer.size.x;
|
let x = match printer.size.x.checked_sub(1) {
|
||||||
if x == 0 {
|
Some(x) => x,
|
||||||
return;
|
None => return,
|
||||||
}
|
};
|
||||||
|
|
||||||
|
|
||||||
printer.with_color(style, |printer| {
|
printer.with_color(style, |printer| {
|
||||||
// Prepare the entire background
|
// Prepare the entire background
|
||||||
printer.print_hline((1, 0), x - 1, " ");
|
printer.print_hline((1, 0), x, " ");
|
||||||
// Draw the borders
|
// Draw the borders
|
||||||
printer.print((0, 0), "<");
|
printer.print((0, 0), "<");
|
||||||
printer.print((x - 1, 0), ">");
|
printer.print((x, 0), ">");
|
||||||
|
|
||||||
let label = &self.items[self.focus()].label;
|
let label = &self.items[self.focus()].label;
|
||||||
|
|
||||||
// And center the text?
|
// And center the text?
|
||||||
let offset = HAlign::Center.get_offset(label.len(), x);
|
let offset = HAlign::Center.get_offset(label.len(), x + 1);
|
||||||
|
|
||||||
printer.print((offset, 0), label);
|
printer.print((offset, 0), label);
|
||||||
});
|
});
|
||||||
@ -445,20 +443,17 @@ impl<T: 'static> View for SelectView<T> {
|
|||||||
// We'll want to show the popup so that the text matches.
|
// We'll want to show the popup so that the text matches.
|
||||||
// It'll be soo cool.
|
// It'll be soo cool.
|
||||||
let item_length = self.items[focus].label.len();
|
let item_length = self.items[focus].label.len();
|
||||||
let text_offset = if self.last_size.x >= item_length {
|
let text_offset =
|
||||||
(self.last_size.x - item_length) / 2
|
(self.last_size.x.saturating_sub(item_length)) / 2;
|
||||||
} else {
|
|
||||||
// We were too small to show the entire item last time.
|
|
||||||
0
|
|
||||||
};
|
|
||||||
// The total offset for the window is:
|
// The total offset for the window is:
|
||||||
// * the last absolute offset at which we drew this view
|
// * the last absolute offset at which we drew this view
|
||||||
// * shifted to the top of the focus (so the line matches)
|
|
||||||
// * shifted to the right of the text offset
|
// * shifted to the right of the text offset
|
||||||
|
// * shifted to the top of the focus (so the line matches)
|
||||||
// * shifted top-left of the border+padding of the popup
|
// * shifted top-left of the border+padding of the popup
|
||||||
let offset = self.last_offset.get() - (0, focus) +
|
let offset = self.last_offset.get();
|
||||||
(text_offset, 0) -
|
let offset = offset + (text_offset, 0);
|
||||||
(2, 1);
|
let offset = offset.saturating_sub((0, focus));
|
||||||
|
let offset = offset.saturating_sub((2, 1));
|
||||||
// And now, we can return the callback.
|
// And now, we can return the callback.
|
||||||
EventResult::with_cb(move |s| {
|
EventResult::with_cb(move |s| {
|
||||||
// The callback will want to work with a fresh Rc
|
// The callback will want to work with a fresh Rc
|
||||||
@ -468,12 +463,11 @@ impl<T: 'static> View for SelectView<T> {
|
|||||||
// A nice effect is that window resizes will keep both
|
// A nice effect is that window resizes will keep both
|
||||||
// layers together.
|
// layers together.
|
||||||
let current_offset = s.screen().offset();
|
let current_offset = s.screen().offset();
|
||||||
let offset = XY::<isize>::from(offset) -
|
let offset = offset.signed() - current_offset;
|
||||||
current_offset;
|
|
||||||
// And finally, put the view in view!
|
// And finally, put the view in view!
|
||||||
s.screen_mut()
|
s.screen_mut().add_layer_at(Position::parent(offset),
|
||||||
.add_layer_at(Position::parent(offset),
|
MenuPopup::new(tree)
|
||||||
MenuPopup::new(tree).focus(focus));
|
.focus(focus));
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
_ => EventResult::Ignored,
|
_ => EventResult::Ignored,
|
||||||
@ -488,7 +482,7 @@ impl<T: 'static> View for SelectView<T> {
|
|||||||
Event::Key(Key::PageUp) => self.focus_up(10),
|
Event::Key(Key::PageUp) => self.focus_up(10),
|
||||||
Event::Key(Key::PageDown) => self.focus_down(10),
|
Event::Key(Key::PageDown) => self.focus_down(10),
|
||||||
Event::Key(Key::Home) => self.focus.set(0),
|
Event::Key(Key::Home) => self.focus.set(0),
|
||||||
Event::Key(Key::End) => self.focus.set(self.items.len() - 1),
|
Event::Key(Key::End) => self.focus.set(self.items.len().saturating_sub(1)),
|
||||||
Event::Key(Key::Enter) if self.on_submit.is_some() => {
|
Event::Key(Key::Enter) if self.on_submit.is_some() => {
|
||||||
let cb = self.on_submit.clone().unwrap();
|
let cb = self.on_submit.clone().unwrap();
|
||||||
let v = self.selection();
|
let v = self.selection();
|
||||||
@ -504,9 +498,10 @@ impl<T: 'static> View for SelectView<T> {
|
|||||||
// the list when we reach the end.
|
// the list when we reach the end.
|
||||||
// This is achieved by chaining twice the iterator
|
// This is achieved by chaining twice the iterator
|
||||||
let iter = self.items.iter().chain(self.items.iter());
|
let iter = self.items.iter().chain(self.items.iter());
|
||||||
if let Some((i, _)) = iter.enumerate()
|
if let Some((i, _)) =
|
||||||
.skip(self.focus() + 1)
|
iter.enumerate()
|
||||||
.find(|&(_, item)| item.label.starts_with(c)) {
|
.skip(self.focus() + 1)
|
||||||
|
.find(|&(_, item)| item.label.starts_with(c)) {
|
||||||
// Apply modulo in case we have a hit
|
// Apply modulo in case we have a hit
|
||||||
// from the chained iterator
|
// from the chained iterator
|
||||||
self.focus.set(i % self.items.len());
|
self.focus.set(i % self.items.len());
|
||||||
|
@ -49,13 +49,13 @@ impl<T: View> ViewWrapper for ShadowView<T> {
|
|||||||
|
|
||||||
fn wrap_required_size(&mut self, req: Vec2) -> Vec2 {
|
fn wrap_required_size(&mut self, req: Vec2) -> Vec2 {
|
||||||
// Make sure req >= offset
|
// Make sure req >= offset
|
||||||
let offset = self.padding().or_min(req);
|
let offset = self.padding();
|
||||||
self.view.required_size(req - offset) + offset
|
self.view.required_size(req.saturating_sub(offset)) + offset
|
||||||
}
|
}
|
||||||
|
|
||||||
fn wrap_layout(&mut self, size: Vec2) {
|
fn wrap_layout(&mut self, size: Vec2) {
|
||||||
let offset = self.padding().or_min(size);
|
let offset = self.padding();
|
||||||
self.view.layout(size - offset);
|
self.view.layout(size.saturating_sub(offset));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn wrap_draw(&self, printer: &Printer) {
|
fn wrap_draw(&self, printer: &Printer) {
|
||||||
@ -74,6 +74,10 @@ impl<T: View> ViewWrapper for ShadowView<T> {
|
|||||||
let h = printer.size.y;
|
let h = printer.size.y;
|
||||||
let w = printer.size.x;
|
let w = printer.size.x;
|
||||||
|
|
||||||
|
if h == 0 || w == 0 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
printer.with_color(ColorStyle::Shadow, |printer| {
|
printer.with_color(ColorStyle::Shadow, |printer| {
|
||||||
printer.print_hline((1, h - 1), w - 1, " ");
|
printer.print_hline((1, h - 1), w - 1, " ");
|
||||||
printer.print_vline((w - 1, 1), h - 1, " ");
|
printer.print_vline((w - 1, 1), h - 1, " ");
|
||||||
@ -81,8 +85,9 @@ impl<T: View> ViewWrapper for ShadowView<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Draw the view background
|
// Draw the view background
|
||||||
let printer =
|
let printer = printer.sub_printer(Vec2::zero(),
|
||||||
printer.sub_printer(Vec2::zero(), printer.size - (1, 1), true);
|
printer.size.saturating_sub((1, 1)),
|
||||||
|
true);
|
||||||
self.view.draw(&printer);
|
self.view.draw(&printer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -192,13 +192,12 @@ impl TextView {
|
|||||||
// We take 1 column for the bar itself + 1 spacing column
|
// We take 1 column for the bar itself + 1 spacing column
|
||||||
scrollbar_width = 2;
|
scrollbar_width = 2;
|
||||||
|
|
||||||
if size.x < scrollbar_width {
|
|
||||||
// Again, this is a lost cause.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we're too high, include a scrollbar_width
|
// If we're too high, include a scrollbar_width
|
||||||
let available = size.x - scrollbar_width;
|
let available = match size.x.checked_sub(scrollbar_width) {
|
||||||
|
Some(s) => s,
|
||||||
|
None => return,
|
||||||
|
};
|
||||||
|
|
||||||
self.rows = LinesIterator::new(&self.content, available).collect();
|
self.rows = LinesIterator::new(&self.content, available).collect();
|
||||||
|
|
||||||
if self.rows.is_empty() && !self.content.is_empty() {
|
if self.rows.is_empty() && !self.content.is_empty() {
|
||||||
|
Loading…
Reference in New Issue
Block a user