mirror of
https://github.com/FliegendeWurst/cursive.git
synced 2024-11-23 17:35:00 +00:00
Fix linear layout in constrained space
This commit is contained in:
parent
12593c8ea8
commit
03c400ad44
@ -1,19 +1,19 @@
|
|||||||
extern crate cursive;
|
extern crate cursive;
|
||||||
|
|
||||||
use cursive::Cursive;
|
use cursive::Cursive;
|
||||||
use cursive::view::{Dialog,TextView,LinearLayout,BoxView};
|
use cursive::view::{BoxView, Dialog, LinearLayout, TextView};
|
||||||
use cursive::align::HAlign;
|
use cursive::align::HAlign;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let mut siv = Cursive::new();
|
let mut siv = Cursive::new();
|
||||||
|
|
||||||
// Some description text
|
// Some description text
|
||||||
let text = "This is a very simple example of linear layout. Two views are present, a short title above, and this text. The text has a fixed width, and the title is centered horizontally.";
|
let text = "This is a very simple example of linear layout. Two views \
|
||||||
|
are present, a short title above, and this text. The text \
|
||||||
|
has a fixed width, and the title is centered horizontally.";
|
||||||
|
|
||||||
// We'll create a dialog with a TextView serving as a title
|
// We'll create a dialog with a TextView serving as a title
|
||||||
siv.add_layer(
|
siv.add_layer(Dialog::new(LinearLayout::vertical()
|
||||||
Dialog::new(
|
|
||||||
LinearLayout::vertical()
|
|
||||||
.child(TextView::new("Title").h_align(HAlign::Center))
|
.child(TextView::new("Title").h_align(HAlign::Center))
|
||||||
// Box the textview, so it doesn't get too wide.
|
// Box the textview, so it doesn't get too wide.
|
||||||
// A 0 height value means it will be unconstrained.
|
// A 0 height value means it will be unconstrained.
|
||||||
|
@ -54,4 +54,11 @@ impl Orientation {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Creates a new `Vec2` with `value` in `self`'s axis.
|
||||||
|
pub fn make_vec(&self, value: usize) -> Vec2 {
|
||||||
|
let mut result = Vec2::zero();
|
||||||
|
*self.get_ref(&mut result) = value;
|
||||||
|
result
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -175,11 +175,11 @@ impl Printer {
|
|||||||
/// Returns a printer on a subset of this one's area.
|
/// Returns a printer on a subset of this one's area.
|
||||||
pub fn sub_printer<S: Into<Vec2>>(&self, offset: S, size: S, focused: bool)
|
pub fn sub_printer<S: Into<Vec2>>(&self, offset: S, size: S, focused: bool)
|
||||||
-> Printer {
|
-> Printer {
|
||||||
let offset_v = offset.into();
|
let offset = offset.into().or_min(self.size);
|
||||||
Printer {
|
Printer {
|
||||||
offset: self.offset + offset_v,
|
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_v, size.into()),
|
size: Vec2::min(self.size - offset, size.into()),
|
||||||
focused: self.focused && focused,
|
focused: self.focused && focused,
|
||||||
theme: self.theme.clone(),
|
theme: self.theme.clone(),
|
||||||
}
|
}
|
||||||
|
16
src/vec.rs
16
src/vec.rs
@ -1,5 +1,6 @@
|
|||||||
//! Points on the 2D character grid.
|
//! Points on the 2D character grid.
|
||||||
use XY;
|
use XY;
|
||||||
|
use orientation::Orientation;
|
||||||
|
|
||||||
use std::ops::{Add, Div, Mul, Sub};
|
use std::ops::{Add, Div, Mul, Sub};
|
||||||
use std::cmp::{Ordering, max, min};
|
use std::cmp::{Ordering, max, min};
|
||||||
@ -70,6 +71,21 @@ impl Vec2 {
|
|||||||
pub fn stack_horizontal(&self, other: &Vec2) -> Vec2 {
|
pub fn stack_horizontal(&self, other: &Vec2) -> Vec2 {
|
||||||
Vec2::new(self.x + other.x, max(self.y, other.y))
|
Vec2::new(self.x + other.x, max(self.y, other.y))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if `self` could fit inside `other`.
|
||||||
|
///
|
||||||
|
/// Shortcut for `self.x <= other.x && self.y <= other.y`.
|
||||||
|
pub fn fits_in<T: Into<Vec2>>(&self, other: T) -> bool {
|
||||||
|
let other = other.into();
|
||||||
|
self.x <= other.x && self.y <= other.y
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a new `Vec2` with the axis `o` set to `value`.
|
||||||
|
pub fn with(&self, o: Orientation, value: usize) -> Self {
|
||||||
|
let mut other = self.clone();
|
||||||
|
*o.get_ref(&mut other) = value;
|
||||||
|
other
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<(i32, i32)> for Vec2 {
|
impl From<(i32, i32)> for Vec2 {
|
||||||
|
@ -68,6 +68,6 @@ impl<T: View> ViewWrapper for BoxView<T> {
|
|||||||
|
|
||||||
Vec2::new(self.size.x.unwrap_or(child_size.x),
|
Vec2::new(self.size.x.unwrap_or(child_size.x),
|
||||||
self.size.y.unwrap_or(child_size.y))
|
self.size.y.unwrap_or(child_size.y))
|
||||||
}
|
}.or_min(req)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,16 +1,20 @@
|
|||||||
|
use XY;
|
||||||
use view::View;
|
use view::View;
|
||||||
|
use view::SizeCache;
|
||||||
use vec::Vec2;
|
use vec::Vec2;
|
||||||
use printer::Printer;
|
use printer::Printer;
|
||||||
use orientation::Orientation;
|
use orientation::Orientation;
|
||||||
use event::{Event, EventResult, Key};
|
use event::{Event, EventResult, Key};
|
||||||
|
|
||||||
|
use std::cmp::min;
|
||||||
|
|
||||||
/// Arranges its children linearly according to its orientation.
|
/// Arranges its children linearly according to its orientation.
|
||||||
pub struct LinearLayout {
|
pub struct LinearLayout {
|
||||||
children: Vec<Child>,
|
children: Vec<Child>,
|
||||||
orientation: Orientation,
|
orientation: Orientation,
|
||||||
focus: usize,
|
focus: usize,
|
||||||
|
|
||||||
last_size: Option<Vec2>,
|
cache: Option<XY<SizeCache>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Child {
|
struct Child {
|
||||||
@ -19,6 +23,13 @@ struct Child {
|
|||||||
weight: usize,
|
weight: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Child {
|
||||||
|
fn get_min_size(&mut self, req: Vec2) -> Vec2 {
|
||||||
|
self.size = self.view.get_min_size(req);
|
||||||
|
self.size
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl LinearLayout {
|
impl LinearLayout {
|
||||||
/// Creates a new layout with the given orientation.
|
/// Creates a new layout with the given orientation.
|
||||||
pub fn new(orientation: Orientation) -> Self {
|
pub fn new(orientation: Orientation) -> Self {
|
||||||
@ -26,7 +37,7 @@ impl LinearLayout {
|
|||||||
children: Vec::new(),
|
children: Vec::new(),
|
||||||
orientation: orientation,
|
orientation: orientation,
|
||||||
focus: 0,
|
focus: 0,
|
||||||
last_size: None,
|
cache: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -46,11 +57,16 @@ impl LinearLayout {
|
|||||||
size: Vec2::zero(),
|
size: Vec2::zero(),
|
||||||
weight: 0,
|
weight: 0,
|
||||||
});
|
});
|
||||||
self.last_size = None;
|
self.invalidate();
|
||||||
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Invalidate the view, to request a layout next time
|
||||||
|
fn invalidate(&mut self) {
|
||||||
|
self.cache = None;
|
||||||
|
}
|
||||||
|
|
||||||
/// Creates a new vertical layout.
|
/// Creates a new vertical layout.
|
||||||
pub fn vertical() -> Self {
|
pub fn vertical() -> Self {
|
||||||
LinearLayout::new(Orientation::Vertical)
|
LinearLayout::new(Orientation::Vertical)
|
||||||
@ -60,6 +76,31 @@ impl LinearLayout {
|
|||||||
pub fn horizontal() -> Self {
|
pub fn horizontal() -> Self {
|
||||||
LinearLayout::new(Orientation::Horizontal)
|
LinearLayout::new(Orientation::Horizontal)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If the cache can be used, return the cached size.
|
||||||
|
// Otherwise, return None.
|
||||||
|
fn get_cache(&self, req: Vec2) -> Option<Vec2> {
|
||||||
|
match self.cache {
|
||||||
|
None => None,
|
||||||
|
Some(ref cache) => {
|
||||||
|
// Is our cache even valid?
|
||||||
|
// Also, is any child invalidating the layout?
|
||||||
|
if cache.x.accept(req.x) && cache.y.accept(req.y) &&
|
||||||
|
self.children_are_sleeping() {
|
||||||
|
Some(cache.map(|s| s.value))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn children_are_sleeping(&self) -> bool {
|
||||||
|
!self.children
|
||||||
|
.iter()
|
||||||
|
.map(|c| &*c.view)
|
||||||
|
.any(View::needs_relayout)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the index of the maximum element.
|
/// Returns the index of the maximum element.
|
||||||
@ -129,23 +170,29 @@ impl View for LinearLayout {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn needs_relayout(&self) -> bool {
|
fn needs_relayout(&self) -> bool {
|
||||||
if self.last_size == None {
|
if self.cache.is_none() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
for child in &self.children {
|
!self.children_are_sleeping()
|
||||||
if child.view.needs_relayout() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn layout(&mut self, size: Vec2) {
|
fn layout(&mut self, size: Vec2) {
|
||||||
// Compute the very minimal required size
|
// If we can get away without breaking a sweat, you can bet we will.
|
||||||
// Look how mean we are: we offer the whole size to every child.
|
if self.get_cache(size).is_none() {
|
||||||
// As if they could get it all.
|
self.get_min_size(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
for child in &mut self.children {
|
||||||
|
// println_stderr!("Child size: {:?}", child.size);
|
||||||
|
child.view.layout(child.size);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
// Need to compute things again...
|
||||||
|
self.get_min_size(size);
|
||||||
|
|
||||||
let min_sizes: Vec<Vec2> = self.children
|
let min_sizes: Vec<Vec2> = self.children
|
||||||
.iter_mut()
|
.iter_mut()
|
||||||
.map(|child| Vec2::min(size, child.view.get_min_size(size)))
|
.map(|child| Vec2::min(size, child.view.get_min_size(size)))
|
||||||
@ -180,26 +227,104 @@ impl View for LinearLayout {
|
|||||||
child.size = child_size;
|
child.size = child_size;
|
||||||
child.view.layout(child_size);
|
child.view.layout(child_size);
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_min_size(&mut self, req: Vec2) -> Vec2 {
|
fn get_min_size(&mut self, req: Vec2) -> Vec2 {
|
||||||
|
// Did anything change since last time?
|
||||||
|
if let Some(size) = self.get_cache(req) {
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
// First, make a naive scenario: everything will work fine.
|
// First, make a naive scenario: everything will work fine.
|
||||||
let sizes: Vec<Vec2> = self.children
|
let sizes: Vec<Vec2> = self.children
|
||||||
.iter_mut()
|
.iter_mut()
|
||||||
.map(|view| view.view.get_min_size(req))
|
.map(|c| c.get_min_size(req))
|
||||||
.collect();
|
.collect();
|
||||||
self.orientation.stack(sizes.iter())
|
// println_stderr!("Ideal sizes: {:?}", sizes);
|
||||||
|
let ideal = self.orientation.stack(sizes.iter());
|
||||||
|
// println_stderr!("Ideal result: {:?}", ideal);
|
||||||
|
|
||||||
|
|
||||||
// Did it work? Champagne!
|
// Does it fit?
|
||||||
|
if ideal.fits_in(req) {
|
||||||
|
// Champagne!
|
||||||
|
self.cache = Some(SizeCache::build(ideal, req));
|
||||||
|
return ideal;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ok, so maybe it didn't.
|
||||||
|
// Budget cuts, everyone.
|
||||||
|
let budget_req = req.with(self.orientation, 1);
|
||||||
|
// println_stderr!("Budget req: {:?}", budget_req);
|
||||||
|
|
||||||
|
let min_sizes: Vec<Vec2> = self.children
|
||||||
|
.iter_mut()
|
||||||
|
.map(|c| c.get_min_size(budget_req))
|
||||||
|
.collect();
|
||||||
|
let desperate = self.orientation.stack(min_sizes.iter());
|
||||||
|
// println_stderr!("Min sizes: {:?}", min_sizes);
|
||||||
|
// println_stderr!("Desperate: {:?}", desperate);
|
||||||
|
|
||||||
|
// I really hope it fits this time...
|
||||||
|
if !desperate.fits_in(req) {
|
||||||
|
// Just give up...
|
||||||
|
// println_stderr!("Seriously? {:?} > {:?}???", desperate, req);
|
||||||
|
self.cache = Some(SizeCache::build(desperate, req));
|
||||||
|
return desperate;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This here is how much we're generously offered
|
||||||
|
let mut available = self.orientation.get(&(req - desperate));
|
||||||
|
// println_stderr!("Available: {:?}", available);
|
||||||
|
|
||||||
|
// Here, we have to make a compromise between the ideal
|
||||||
|
// and the desperate solutions.
|
||||||
|
let mut overweight: Vec<(usize, usize)> = sizes.iter()
|
||||||
|
.map(|v| self.orientation.get(v))
|
||||||
|
.zip(min_sizes.iter().map(|v| self.orientation.get(v)))
|
||||||
|
.map(|(a, b)| a - b)
|
||||||
|
.enumerate()
|
||||||
|
.collect();
|
||||||
|
// println_stderr!("Overweight: {:?}", overweight);
|
||||||
|
|
||||||
|
// So... distribute `available` to reduce the overweight...
|
||||||
|
// TODO: use child weight in the distribution...
|
||||||
|
overweight.sort_by_key(|&(_, weight)| weight);
|
||||||
|
let mut allocations = vec![0; overweight.len()];
|
||||||
|
|
||||||
|
for (i, &(j, weight)) in overweight.iter().enumerate() {
|
||||||
|
let remaining = overweight.len() - i;
|
||||||
|
let budget = available / remaining;
|
||||||
|
let spent = min(budget, weight);
|
||||||
|
allocations[j] = spent;
|
||||||
|
available -= spent;
|
||||||
|
}
|
||||||
|
// println_stderr!("Allocations: {:?}", allocations);
|
||||||
|
|
||||||
|
// Final lengths are the minimum ones + allocations
|
||||||
|
let final_lengths: Vec<Vec2> = min_sizes.iter()
|
||||||
|
.map(|v| self.orientation.get(v))
|
||||||
|
.zip(allocations.iter())
|
||||||
|
.map(|(a, b)| a + b)
|
||||||
|
.map(|l| req.with(self.orientation, l))
|
||||||
|
.collect();
|
||||||
|
// println_stderr!("Final sizes: {:?}", final_lengths);
|
||||||
|
|
||||||
|
let final_sizes: Vec<Vec2> = self.children
|
||||||
|
.iter_mut()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(i, c)| {
|
||||||
|
c.get_min_size(final_lengths[i])
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
// println_stderr!("Final sizes2: {:?}", final_sizes);
|
||||||
|
|
||||||
|
|
||||||
// TODO: Ok, so maybe it didn't.
|
let compromise = self.orientation.stack(final_sizes.iter());
|
||||||
// Last chance: did someone lie about his needs?
|
self.cache = Some(SizeCache::build(compromise, req));
|
||||||
// Could we squash him a little?
|
|
||||||
// (Maybe he'll just scroll and it'll be fine?)
|
|
||||||
|
|
||||||
// Find out who's fluid, if any.
|
compromise
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_event(&mut self, event: Event) -> EventResult {
|
fn on_event(&mut self, event: Event) -> EventResult {
|
||||||
|
@ -30,6 +30,7 @@ mod tracked_view;
|
|||||||
|
|
||||||
use std::any::Any;
|
use std::any::Any;
|
||||||
|
|
||||||
|
use XY;
|
||||||
use event::{Event, EventResult};
|
use event::{Event, EventResult};
|
||||||
use vec::Vec2;
|
use vec::Vec2;
|
||||||
use printer::Printer;
|
use printer::Printer;
|
||||||
@ -105,6 +106,49 @@ pub trait View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Cache around a one-dimensional layout result
|
||||||
|
#[derive(PartialEq, Debug, Clone, Copy)]
|
||||||
|
pub struct SizeCache {
|
||||||
|
/// Cached value
|
||||||
|
pub value: usize,
|
||||||
|
/// `true` if the last size was constrained.
|
||||||
|
///
|
||||||
|
/// If unconstrained, any request larger than this value
|
||||||
|
/// would return the same size.
|
||||||
|
pub constrained: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SizeCache {
|
||||||
|
/// Creates a new sized cache
|
||||||
|
pub fn new(value: usize, constrained: bool) -> Self {
|
||||||
|
SizeCache {
|
||||||
|
value: value,
|
||||||
|
constrained: constrained,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if `self` is still valid for the given `request`.
|
||||||
|
pub fn accept(&self, request: usize) -> bool {
|
||||||
|
if request < self.value {
|
||||||
|
false
|
||||||
|
} else if request == self.value {
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
!self.constrained
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a new bi-dimensional cache.
|
||||||
|
///
|
||||||
|
/// * `size` must fit inside `req`.
|
||||||
|
/// * for each dimension, `constrained = (size == req)`
|
||||||
|
fn build(size: Vec2, req: Vec2) -> XY<Self> {
|
||||||
|
XY::new(SizeCache::new(size.x, size.x == req.x),
|
||||||
|
SizeCache::new(size.y, size.y == req.y))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Selects a single view (if any) in the tree.
|
/// Selects a single view (if any) in the tree.
|
||||||
pub enum Selector<'a> {
|
pub enum Selector<'a> {
|
||||||
/// Selects a view from its ID
|
/// Selects a view from its ID
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
use XY;
|
use XY;
|
||||||
use vec::Vec2;
|
use vec::Vec2;
|
||||||
use view::View;
|
use view::View;
|
||||||
|
use view::SizeCache;
|
||||||
use printer::Printer;
|
use printer::Printer;
|
||||||
use align::*;
|
use align::*;
|
||||||
use event::*;
|
use event::*;
|
||||||
@ -9,38 +10,6 @@ use super::scroll::ScrollBase;
|
|||||||
use unicode_width::UnicodeWidthStr;
|
use unicode_width::UnicodeWidthStr;
|
||||||
use unicode_segmentation::UnicodeSegmentation;
|
use unicode_segmentation::UnicodeSegmentation;
|
||||||
|
|
||||||
#[derive(PartialEq, Debug, Clone)]
|
|
||||||
struct SizeCache {
|
|
||||||
value: usize,
|
|
||||||
// If unconstrained, any request larger than this value
|
|
||||||
// would return the same size.
|
|
||||||
constrained: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SizeCache {
|
|
||||||
fn new(value: usize, constrained: bool) -> Self {
|
|
||||||
SizeCache {
|
|
||||||
value: value,
|
|
||||||
constrained: constrained,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn accept(&self, size: usize) -> bool {
|
|
||||||
if size < self.value {
|
|
||||||
false
|
|
||||||
} else if size == self.value {
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
!self.constrained
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn cache_size(size: Vec2, req: Vec2) -> XY<SizeCache> {
|
|
||||||
XY::new(SizeCache::new(size.x, size.x == req.x),
|
|
||||||
SizeCache::new(size.y, size.y == req.y))
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A simple view showing a fixed text
|
/// A simple view showing a fixed text
|
||||||
pub struct TextView {
|
pub struct TextView {
|
||||||
@ -147,7 +116,7 @@ impl TextView {
|
|||||||
// Our resulting size.
|
// Our resulting size.
|
||||||
let my_size =
|
let my_size =
|
||||||
size.or_min((self.width.unwrap_or(0), self.rows.len()));
|
size.or_min((self.width.unwrap_or(0), self.rows.len()));
|
||||||
self.last_size = Some(cache_size(my_size, size));
|
self.last_size = Some(SizeCache::build(my_size, size));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -267,7 +236,7 @@ impl View for TextView {
|
|||||||
|
|
||||||
fn get_min_size(&mut self, size: Vec2) -> Vec2 {
|
fn get_min_size(&mut self, size: Vec2) -> Vec2 {
|
||||||
self.compute_rows(size);
|
self.compute_rows(size);
|
||||||
Vec2::new(self.width.unwrap_or(0), self.rows.len())
|
size.or_min((self.width.unwrap_or(0), self.rows.len()))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn take_focus(&mut self) -> bool {
|
fn take_focus(&mut self) -> bool {
|
||||||
|
@ -15,6 +15,11 @@ impl<T> XY<T> {
|
|||||||
XY { x: x, y: y }
|
XY { x: x, y: y }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Creates a new `XY` by applying `f` to `x` and `y`.
|
||||||
|
pub fn map<U, F: Fn(T) -> U>(self, f: F) -> XY<U> {
|
||||||
|
XY::new(f(self.x), f(self.y))
|
||||||
|
}
|
||||||
|
|
||||||
/// Destructure self into a pair.
|
/// Destructure self into a pair.
|
||||||
pub fn pair(self) -> (T, T) {
|
pub fn pair(self) -> (T, T) {
|
||||||
(self.x, self.y)
|
(self.x, self.y)
|
||||||
|
Loading…
Reference in New Issue
Block a user