Merge branch 'master' of github.com:dankamongmen/notcurses

pull/1239/head
nick black 4 years ago
commit 1303e5e310
No known key found for this signature in database
GPG Key ID: 5F43400C21CBFACC

@ -9,7 +9,12 @@ use crate::{
impl NcCell {
/// New NcCell, expects a [char], [NcStyleMask] and [NcChannelPair].
#[inline]
pub const fn with_all(ch: char, width: u8, stylemask: NcStyleMask, channels: NcChannelPair) -> Self {
pub const fn with_all(
ch: char,
width: u8,
stylemask: NcStyleMask,
channels: NcChannelPair,
) -> Self {
NcCell {
gcluster: ch as u32,
gcluster_backstop: 0 as NcEgcBackstop,

@ -0,0 +1,7 @@
//! `NcDimension`, `NcOffset`
/// A dimension in rows or columns. Can't be negative.
pub type NcDimension = u32;
/// An offset in rows or columns. Can be negative.
pub type NcOffset = i32;

@ -2,12 +2,8 @@
use std::mem::transmute;
// NOTE: These defined macros can't be handled by bindgen yet, see:
// - https://github.com/rust-lang/rust-bindgen/issues/316
// - https://github.com/jethrogb/rust-cexpr/pull/15
// Waiting for: https://github.com/rust-lang/rust/issues/53605
//const fn suppuabize(w: u32) -> char {
// NOTE: Waiting for: https://github.com/rust-lang/rust/issues/53605
// const fn suppuabize(w: u32) -> char {
const fn suppuabize(w: u32) -> u32 {
// unsafe { transmute(w + 0x100000) }
w + 0x100000
@ -134,3 +130,7 @@ pub const NCKEY_RELEASE: char = unsafe { transmute(suppuabize(212)) };
pub const NCKEY_SCROLL_UP: char = NCKEY_BUTTON4;
pub const NCKEY_SCROLL_DOWN: char = NCKEY_BUTTON5;
pub const NCKEY_RETURN: char = NCKEY_ENTER;
// Aliases, from the 128 characters common to ASCII+UTF8
pub const NCKEY_ESC: char = unsafe { transmute(0x1b) };
pub const NCKEY_SPACE: char = unsafe { transmute(0x20) };

@ -114,6 +114,7 @@ pub use bindings::*;
mod cells;
mod channel;
mod dimension;
mod direct;
mod error;
mod file;
@ -131,12 +132,13 @@ mod time;
mod visual;
mod widgets;
pub use crate::input::*;
pub use cells::*;
pub use channel::*;
pub use dimension::*;
pub use direct::*;
pub use error::*;
pub use file::*;
pub use input::*;
pub use key::*;
pub use keycodes::*;
pub use macros::*;

@ -14,16 +14,14 @@ macro_rules! sleep {
/// milliseconds and returns the result of [notcurses_render].
#[macro_export]
macro_rules! rsleep {
($nc:expr, $ms:expr) => {
{
let mut res: NcResult = 0;
unsafe {
res = crate::notcurses_render($nc);
}
std::thread::sleep(std::time::Duration::from_millis($ms));
res
($nc:expr, $ms:expr) => {{
let mut res: NcResult = 0;
unsafe {
res = crate::notcurses_render($nc);
}
};
std::thread::sleep(std::time::Duration::from_millis($ms));
res
}};
}
/// Converts `&str` to `*mut CString`, for when `*const c_char` is needed.

@ -3,8 +3,8 @@
use core::ptr::{null, null_mut};
use crate::{
notcurses_init, NcLogLevel, NcPlane, NcResult, Notcurses, NotcursesOptions,
NCOPTION_NO_ALTERNATE_SCREEN, NCOPTION_SUPPRESS_BANNERS,
notcurses_init, sigset_t, NcInput, NcLogLevel, NcPlane, NcResult, NcTime, Notcurses,
NotcursesOptions, NCOPTION_NO_ALTERNATE_SCREEN, NCOPTION_SUPPRESS_BANNERS,
};
/// # `NotcursesOptions` Constructors
@ -116,6 +116,26 @@ impl Notcurses {
/// # `Notcurses` methods
impl Notcurses {
///
pub fn getc(&mut self, time: &NcTime, sigmask: &mut sigset_t, input: &mut NcInput) -> char {
unsafe { core::char::from_u32_unchecked(crate::notcurses_getc(self, time, sigmask, input)) }
}
///
pub fn getc_nblock(&mut self, input: &mut NcInput) -> char {
crate::notcurses_getc_nblock(self, input)
}
///
pub fn getc_nblocking(&mut self, input: &mut NcInput) -> char {
crate::notcurses_getc_nblocking(self, input)
}
///
pub fn render(&mut self) -> NcResult {
unsafe { crate::notcurses_render(self) }
}
/// Returns a mutable reference to the standard [NcPlane] for this terminal.
///
/// The standard plane always exists, and its origin is always at the
@ -136,9 +156,4 @@ impl Notcurses {
pub fn stop(&mut self) -> NcResult {
unsafe { crate::notcurses_stop(self) }
}
///
pub fn render(&mut self) -> NcResult {
unsafe { crate::notcurses_render(self) }
}
}

@ -10,7 +10,9 @@ use crate::{
notcurses_stdplane,
notcurses_stdplane_const,
NcAlign,
NcDimension,
NcInput,
NcOffset,
NcPlane,
NcTime,
Notcurses,
@ -18,9 +20,10 @@ use crate::{
NCALIGN_LEFT,
};
/// return the offset into 'availcols' at which 'cols' ought be output given the requirements of 'align'
/// Returns the offset into 'availcols' at which 'cols' ought be output given
/// the requirements of 'align'.
#[inline]
pub fn notcurses_align(availcols: i32, align: NcAlign, cols: i32) -> i32 {
pub fn notcurses_align(availcols: NcDimension, align: NcAlign, cols: NcDimension) -> NcOffset {
if align == NCALIGN_LEFT {
return 0;
}
@ -28,14 +31,13 @@ pub fn notcurses_align(availcols: i32, align: NcAlign, cols: i32) -> i32 {
return 0;
}
if align == NCALIGN_CENTER {
return (availcols - cols) / 2;
return ((availcols - cols) / 2) as NcOffset;
}
availcols - cols // NCALIGN_RIGHT
(availcols - cols) as NcOffset // NCALIGN_RIGHT
}
/// 'input' may be NULL if the caller is uninterested in event details.
/// If no event is ready, returns 0.
// TODO: use pakr-signals
#[inline]
pub fn notcurses_getc_nblock(nc: &mut Notcurses, input: &mut NcInput) -> char {
unsafe {
@ -63,10 +65,14 @@ pub fn notcurses_getc_nblocking(nc: &mut Notcurses, input: &mut NcInput) -> char
/// notcurses_stdplane(), plus free bonus dimensions written to non-NULL y/x!
#[inline]
pub fn notcurses_stddim_yx<'a>(nc: &mut Notcurses, y: &mut i32, x: &mut i32) -> &'a mut NcPlane {
pub fn notcurses_stddim_yx<'a>(
nc: &mut Notcurses,
y: &mut NcDimension,
x: &mut NcDimension,
) -> &'a mut NcPlane {
unsafe {
let s = notcurses_stdplane(nc);
ncplane_dim_yx(s, y, x);
ncplane_dim_yx(s, &mut (*y as i32), &mut (*x as i32));
&mut *s
}
}

@ -1,35 +1,60 @@
//! `NcPlane*` methods and associated functions.
use core::ptr::{null, null_mut};
use std::ffi::CStr;
use crate::{cstring, NcAlign, NcCell, NcPlane, NcPlaneOptions, NcResult, Notcurses};
use crate::{
cstring, NcAlign, NcCell, NcChannelPair, NcDimension, NcEgc, NcOffset, NcPlane, NcPlaneOptions,
NcResult, NcStyleMask, Notcurses,
};
/// # `NcPlaneOptions` Constructors
impl NcPlaneOptions {
/// New NcPlaneOptions using the horizontal x.
pub fn new(y: i32, x: i32, rows: u32, cols: u32) -> Self {
pub fn new(y: NcOffset, x: NcOffset, rows: NcDimension, cols: NcDimension) -> Self {
Self::with_flags(y, x, rows, cols, 0)
}
/// New NcPlaneOptions with horizontal alignment.
pub fn new_halign(y: i32, align: NcAlign, rows: u32, cols: u32) -> Self {
Self::with_flags(
y,
align as i32,
rows,
cols,
crate::NCPLANE_OPTION_HORALIGNED,
)
pub fn new_aligned(y: NcOffset, align: NcAlign, rows: NcDimension, cols: NcDimension) -> Self {
Self::with_flags_aligned(y, align, rows, cols, crate::NCPLANE_OPTION_HORALIGNED)
}
/// New NcPlaneOptions, with flags.
pub fn with_flags(
y: NcOffset,
x: NcOffset,
rows: NcDimension,
cols: NcDimension,
flags: u64,
) -> Self {
NcPlaneOptions {
y: y as i32,
x: x as i32,
rows: rows as i32,
cols: cols as i32,
userptr: null_mut(),
name: null(),
resizecb: None,
flags,
}
}
/// New NcPlaneOptions, with flags and horizontal alignment.
///
/// Note: If you use [NCPLANE_OPTION_HORALIGNED] flag, you must provide
/// the [NcAlign] value to the `x` parameter, casted to `i32`.
pub fn with_flags(y: i32, x: i32, rows: u32, cols: u32, flags: u64) -> Self {
/// Note: Already includes the
/// [NCPLANE_OPTION_HORALIGNED][crate::NCPLANE_OPTION_HORALIGNED] flag.
pub fn with_flags_aligned(
y: NcOffset,
align: NcAlign,
rows: NcDimension,
cols: NcDimension,
flags: u64,
) -> Self {
let flags = crate::NCPLANE_OPTION_HORALIGNED | flags;
NcPlaneOptions {
y,
x,
y: y as i32,
x: align as i32,
rows: rows as i32,
cols: cols as i32,
userptr: null_mut(),
@ -40,12 +65,18 @@ impl NcPlaneOptions {
}
}
/// # `NcPlane` Constructors
/// # `NcPlane` constructors and destructors
impl NcPlane {
/// New NcPlane.
///
/// The returned plane will be the top, bottom, and root of this new pile.
pub fn new<'a>(nc: &mut Notcurses, y: i32, x: i32, rows: u32, cols: u32) -> &'a mut NcPlane {
pub fn new<'a>(
nc: &mut Notcurses,
y: NcOffset,
x: NcOffset,
rows: NcDimension,
cols: NcDimension,
) -> &'a mut NcPlane {
let options = NcPlaneOptions::new(y, x, rows, cols);
unsafe { &mut *crate::ncpile_create(nc, &options) }
}
@ -60,10 +91,10 @@ impl NcPlane {
/// New NcPlane, bound to another NcPlane.
pub fn new_bound<'a>(
bound_to: &mut NcPlane,
y: i32,
x: i32,
rows: u32,
cols: u32,
y: NcOffset,
x: NcOffset,
rows: NcDimension,
cols: NcDimension,
) -> &'a mut NcPlane {
let options = NcPlaneOptions::new(y, x, rows, cols);
unsafe { &mut *crate::ncplane_create(bound_to, &options) }
@ -72,8 +103,11 @@ impl NcPlane {
/// New NcPlane, bound to another plane, expects an [NcPlaneOptions] struct.
///
/// The returned plane will be the top, bottom, and root of this new pile.
pub fn with_options_bound<'a>(nc: &mut Notcurses, options: &NcPlaneOptions) -> &'a mut NcPlane {
unsafe { &mut *crate::ncpile_create(nc, options) }
pub fn with_options_bound<'a>(
bound_to: &mut NcPlane,
options: &NcPlaneOptions,
) -> &'a mut NcPlane {
unsafe { &mut *crate::ncplane_create(bound_to, options) }
}
/// New NcPlane, with the same dimensions of the terminal.
@ -84,9 +118,19 @@ impl NcPlane {
crate::notcurses_term_dim_yx(nc, &mut trows, &mut tcols);
assert![(trows > 0) & (tcols > 0)];
unsafe {
&mut *crate::ncpile_create(nc, &NcPlaneOptions::new(0, 0, trows as u32, tcols as u32))
&mut *crate::ncpile_create(
nc,
&NcPlaneOptions::new(0, 0, trows as NcDimension, tcols as NcDimension),
)
}
}
/// Destroys the NcPlane.
/// None of its contents will be visible after the next render call.
/// It is an error to attempt to destroy the standard plane.
pub fn destroy(&mut self) -> NcResult {
unsafe { crate::ncplane_destroy(self) }
}
}
/// # `NcPlane` Methods
@ -95,44 +139,92 @@ impl NcPlane {
/// Returns the current position of the cursor within the [NcPlane].
///
/// Unlike [ncplane_cursor_yx] which uses `i32`, this uses [u32].
//
// NOTE: y and/or x may be NULL.
// FIXME: CHECK for NULL and return Some() or None.
pub fn cursor_yx(&self) -> (i32, i32) {
// maybe check for null and return Some() or None?
pub fn cursor_yx(&self) -> (NcDimension, NcDimension) {
let (mut y, mut x) = (0, 0);
unsafe { crate::ncplane_cursor_yx(self, &mut y, &mut x) };
(y, x)
(y as NcDimension, x as NcDimension)
}
/// Returns the current row of the cursor within the [NcPlane].
pub fn cursor_y(&self) -> i32 {
pub fn cursor_y(&self) -> NcDimension {
self.cursor_yx().0
}
/// Returns the current column of the cursor within the [NcPlane].
pub fn cursor_x(&self) -> i32 {
pub fn cursor_x(&self) -> NcDimension {
self.cursor_yx().1
}
/// Moves the cursor to the specified position within the NcPlane.
///
/// The cursor doesn't need to be visible.
///
/// Parameters exceeding the plane's dimensions will result in an error,
/// and the cursor position will remain unchanged.
pub fn cursor_move_yx(&mut self, y: NcDimension, x: NcDimension) -> NcResult {
unsafe { crate::ncplane_cursor_move_yx(self, y as i32, x as i32) }
}
/// Moves the cursor the number of rows specified (forward or backwards).
///
/// It will error if the target row exceeds the plane dimensions.
pub fn cursor_move_rows(&mut self, rows: NcOffset) -> NcResult {
let (y, x) = self.cursor_yx();
self.cursor_move_yx((y as NcOffset + rows) as NcDimension, x)
}
/// Moves the cursor the number of columns specified (forward or backwards).
///
/// It will error if the target column exceeds the plane dimensions.
// TODO: maybe in this case it can improve
pub fn cursor_move_cols(&mut self, cols: NcOffset) -> NcResult {
let (y, x) = self.cursor_yx();
self.cursor_move_yx(y, (x as NcOffset + cols) as NcDimension)
}
/// Moves the cursor to 0, 0.
pub fn cursor_home(&mut self) {
unsafe {
crate::ncplane_home(self);
}
}
// Size --------------------------------------------------------------------
/// Returns the column at which 'cols' columns ought start in order to be aligned according to 'align' within ncplane 'n'. Returns INT_MAX on invalid 'align'. Undefined behavior on negative 'cols'.
pub fn align(&mut self, align: NcAlign, cols: NcDimension) -> NcResult {
crate::ncplane_align(self, align, cols)
}
/// Return the dimensions of this [NcPlane].
///
/// Unlike [ncplane_dim_yx] which uses `i32`, this uses [u32].
pub fn dim_yx(&self) -> (u32, u32) {
/// Unlike [ncplane_dim_yx][crate::ncplane_dim_yx] which uses `i32`,
/// this uses [u32].
pub fn dim_yx(&self) -> (NcDimension, NcDimension) {
let (mut y, mut x) = (0, 0);
unsafe { crate::ncplane_dim_yx(self, &mut y, &mut x) };
(y as u32, x as u32)
(y as NcDimension, x as NcDimension)
}
/// Return the rows of this [NcPlane].
pub fn dim_y(&self) -> u32 {
pub fn dim_y(&self) -> NcDimension {
self.dim_yx().0
}
/// Return the columns of this [NcPlane].
pub fn dim_x(&self) -> u32 {
pub fn dim_x(&self) -> NcDimension {
self.dim_yx().1
}
/// Return the rows of this [NcPlane].
pub fn rows(&self) -> NcDimension {
self.dim_yx().0
}
/// Return the cols of this [NcPlane].
pub fn cols(&self) -> NcDimension {
self.dim_yx().1
}
@ -150,7 +242,160 @@ impl NcPlane {
unsafe { crate::ncplane_set_scrolling(self, scroll) }
}
// TODO: resize
/// Resizes the NcPlane.
///
/// The four parameters 'keep_y', 'keep_x', 'keep_len_y', and 'keep_len_x'
/// defines a subset of the NcPlane to keep unchanged. This may be a section
/// of size 0.
///
/// 'keep_x' and 'keep_y' are relative to the NcPlane. They must specify a
/// coordinate within the ncplane's totality. If either of 'keep_len_y' or
/// 'keep_len_x' is non-zero, both must be non-zero.
///
/// 'y_off' and 'x_off' are relative to 'keep_y' and 'keep_x', and place the
/// upper-left corner of the resized NcPlane.
///
/// 'y_len' and 'x_len' are the dimensions of the NcPlane after resizing.
/// 'y_len' must be greater than or equal to 'keep_len_y',
/// and 'x_len' must be greater than or equal to 'keeplenx'.
///
/// It is an error to attempt to resize the standard plane.
pub fn resize(
&mut self,
keep_y: NcDimension,
keep_x: NcDimension,
keep_len_y: NcDimension,
keep_len_x: NcDimension,
y_off: NcOffset,
x_off: NcOffset,
y_len: NcDimension,
x_len: NcDimension,
) -> NcResult {
unsafe {
crate::ncplane_resize(
self,
keep_y as i32,
keep_x as i32,
keep_len_y as i32,
keep_len_x as i32,
y_off as i32,
x_off as i32,
y_len as i32,
x_len as i32,
)
}
}
/// Realigns this NcPlane against its parent, using the alignment specified
/// at creation time. Suitable for use as a 'resizecb'.
pub fn resize_realign(&mut self) -> NcResult {
unsafe { crate::ncplane_resize_realign(self) }
}
/// Resizes the NcPlane, retaining what data we can (everything, unless we're
/// shrinking in some dimension). Keeps the origin where it is.
pub fn resize_simple(&mut self, y_len: NcDimension, x_len: NcDimension) -> NcResult {
crate::ncplane_resize_simple(self, y_len as u32, x_len as u32)
}
/// Returns the NcPlane's current resize callback.
pub fn resizecb(&self) -> Option<unsafe extern "C" fn(*mut NcPlane) -> NcResult> {
unsafe { crate::ncplane_resizecb(self) }
}
// Read -------------------------------------------------------------------
/// Retrieves the current contents of the [NcCell] under the cursor,
/// returning the [NcEgc] and writing out the [NcStyleMask] and the [NcChannelPair].
///
/// This NcEgc must be freed by the caller.
pub fn at_cursor(
&mut self,
stylemask: &mut NcStyleMask,
channels: &mut NcChannelPair,
) -> Option<NcEgc> {
let egc = unsafe { crate::ncplane_at_cursor(self, stylemask, channels) };
if egc.is_null() {
return None;
}
let egc = core::char::from_u32(unsafe { *egc } as u32).expect("wrong char");
Some(egc)
}
/// Retrieves the current contents of the [NcCell] under the cursor.
///
/// This NcCell is invalidated if the associated NcPlane is destroyed.
pub fn at_cursor_cell(&mut self, cell: &mut NcCell) -> NcResult {
crate::ncplane_at_cursor_cell(self, cell)
}
/// Retrieves the current contents of the specified [NcCell], returning the
/// [NcEgc] and writing out the [NcStyleMask] and the [NcChannelPair].
///
/// This NcEgc must be freed by the caller.
pub fn at_yx(
&mut self,
y: i32,
x: i32,
stylemask: &mut NcStyleMask,
channels: &mut NcChannelPair,
) -> Option<NcEgc> {
let egc = unsafe { crate::ncplane_at_yx(self, y, x, stylemask, channels) };
if egc.is_null() {
return None;
}
let egc = core::char::from_u32(unsafe { *egc } as u32).expect("wrong char");
Some(egc)
}
/// Extracts this NcPlane's base [NcCell] into 'cell'.
///
/// The reference is invalidated if the NcPlane is destroyed.
pub fn base(&mut self, cell: &mut NcCell) -> NcResult {
unsafe { crate::ncplane_base(self, cell) }
}
/// Gets the current ChannelPair for this NcPlane.
pub fn channels(&self) -> NcChannelPair {
unsafe { crate::ncplane_channels(self) }
}
/// Creates a flat string from the NcEgc's of the selected region of the
/// NcPlane.
///
/// Starts at the plane's 'beg_y' * 'beg_x' coordinates (which must lie on
/// the plane), continuing for 'len_y' x 'len_x' cells.
///
/// If either of 'through_y' or 'through_x' are true, then 'len_y' or 'len_x',
/// will ignored respectively, and will go through the boundary of the plane.
pub fn contents(
&self,
beg_y: NcDimension,
beg_x: NcDimension,
len_y: NcDimension,
len_x: NcDimension,
through_y: bool,
through_x: bool,
) -> String {
let (mut len_y, mut len_x) = (len_y as i32, len_x as i32);
if through_y {
len_y = -1;
}
if through_x {
len_x = -1;
}
unsafe {
CStr::from_ptr(crate::ncplane_contents(
self,
beg_y as i32,
beg_x as i32,
len_y,
len_x,
))
.to_string_lossy()
.into_owned()
}
}
// Write -------------------------------------------------------------------
@ -169,8 +414,8 @@ impl NcPlane {
/// The new NcCell must already be associated with the Plane.
/// On success, returns the number of columns the cursor was advanced.
/// On failure, -1 is returned.
pub fn putc_yx(&mut self, y: i32, x: i32, cell: &NcCell) -> NcResult {
unsafe { crate::ncplane_putc_yx(self, y, x, cell) }
pub fn putc_yx(&mut self, y: NcDimension, x: NcDimension, cell: &NcCell) -> NcResult {
unsafe { crate::ncplane_putc_yx(self, y as i32, x as i32, cell) }
}
/// Replaces the NcCell at the current coordinates with the provided NcCell,
@ -178,20 +423,49 @@ impl NcPlane {
///
/// The new NcCell must already be associated with the Plane.
/// On success, returns the number of columns the cursor was advanced.
/// On failure, -1 is returned.
pub fn putc(&mut self, cell: &NcCell) -> NcResult {
crate::ncplane_putc(self, cell)
}
/// Writes a series of [NcEgc]s to the current location, using the current style.
/// Calls ncplane_putchar_yx() at the current cursor location.
pub fn putchar(&mut self, ch: char) -> NcResult {
crate::ncplane_putchar(self, ch)
}
// TODO: call put_egc
// /// Replaces the [NcEgc][crate::NcEgc] to the current location, but retain
// /// the styling. The current styling of the plane will not be changed.
// pub fn putchar_stained(&mut self, y: NcDimension, x: NcDimension, ch: char) -> NcResult {
// crate::ncplane_putchar_stained(self, ch)
// }
/// Replaces the [NcEgc][crate::NcEgc], but retain the styling.
/// The current styling of the plane will not be changed.
pub fn putchar_yx(&mut self, y: NcDimension, x: NcDimension, ch: char) -> NcResult {
crate::ncplane_putchar_yx(self, y, x, ch)
}
/// Writes a series of [NcEgc][crate::NcEgc]s to the current location,
/// using the current style.
///
/// Advances the cursor by some positive number of columns (though not beyond
/// the end of the plane); this number is returned on success.
///
/// On error, a non-positive number is returned, indicating the number of
/// columns which were written before the error.
pub fn putstr(&mut self, string: &str) -> NcResult {
crate::ncplane_putstr(self, string)
}
// TODO: Stained Replace a string's worth of glyphs at the current cursor location, but retain the styling. The current styling of the plane will not be changed
/// Writes a series of [NcEgc][crate::NcEgc]s to the current location, but
/// retain the styling.
/// The current styling of the plane will not be changed.
pub fn putstr_stained(&mut self, string: &str) -> NcResult {
unsafe { crate::ncplane_putstr_stained(self, cstring![string]) }
}
/// Write a string, which is a series of [NcEgc]s, to the current location,
/// using the current style.
/// Write a string, which is a series of [NcEgc][crate::NcEgc]s, to the
/// current location, using the current style.
///
/// They will be interpreted as a series of columns (according to the
/// definition of `ncplane_putc()`).
@ -201,19 +475,108 @@ impl NcPlane {
///
/// On error, a non-positive number is returned, indicating the number of
/// columns which were written before the error.
pub fn putstr_yx(&mut self, y: i32, x: i32, string: &str) -> NcResult {
unsafe { crate::ncplane_putstr_yx(self, y, x, cstring![string]) }
pub fn putstr_yx(&mut self, y: NcDimension, x: NcDimension, string: &str) -> NcResult {
unsafe { crate::ncplane_putstr_yx(self, y as i32, x as i32, cstring![string]) }
}
// Pile --------------------------------------------------------------------
/// Returns the bottommost [NcPlane] of the pile that contains this [NnPlane].
// CHECK:
/// Returns the bottommost [NcPlane] of the pile that contains this [NcPlane].
pub fn bottom<'a>(&mut self) -> &'a mut NcPlane {
unsafe { &mut *crate::ncpile_bottom(self) }
}
/// Returns the topmost [NcPlane] of the pile that contains this [NnPlane].
/// Returns the topmost [NcPlane] of the pile that contains this [NcPlane].
pub fn top<'a>(&mut self) -> &'a mut NcPlane {
unsafe { &mut *crate::ncpile_top(self) }
}
/// Makes the physical screen match the last rendered frame from the pile of
/// which this NcPlane is a part.
///
/// This is a blocking call. Don't call this before the pile has been
/// rendered (doing so will likely result in a blank screen).
pub fn rasterize<'a>(&mut self) -> NcResult {
unsafe { crate::ncpile_rasterize(self) }
}
/// Renders the pile of which this NcPlane is a part.
/// Rendering this pile again will blow away the render.
/// To actually write out the render, call ncpile_rasterize().
pub fn render<'a>(&mut self) -> NcResult {
unsafe { crate::ncpile_render(self) }
}
// Plane -------------------------------------------------------------------
// move_above
// move_below
// move_bottom
// move_top
/// Duplicates this NcPlane.
/// The new plane will have the same geometry, will duplicate all content,
/// and will start with the same rendering state.
///
/// The new plane will be immediately above the old one on the z axis,
/// and will be bound to the same parent. Bound planes are not duplicated;
/// the new plane is bound to the current parent, but has no bound planes.
// TODO: deal with the opaque field, currently giving a null_mut pointer.
pub fn dup<'a>(&'a mut self) -> &'a mut NcPlane {
unsafe { &mut *crate::ncplane_dup(self, null_mut()) }
}
/// Moves this NcPlane relative to the standard plane, or the plane to
/// which it is bound.
///
/// It is an error to attempt to move the standard plane.
// CHECK: whether a negative offset is valid
pub fn move_yx(&mut self, y: NcOffset, x: NcOffset) -> NcResult {
unsafe { crate::ncplane_move_yx(self, y, x) }
}
/// Returns the NcPlane above this one, or None if already at the top.
pub fn above<'a>(&'a mut self) -> Option<&'a mut NcPlane> {
let plane = unsafe { crate::ncplane_above(self) };
if plane.is_null() {
return None;
}
Some(unsafe { &mut *plane })
}
/// Returns the NcPlane below this one, or None if already at the bottom.
pub fn below<'a>(&'a mut self) -> Option<&'a mut NcPlane> {
let plane = unsafe { crate::ncplane_below(self) };
if plane.is_null() {
return None;
}
Some(unsafe { &mut *plane })
}
/// Gets the parent to which this NcPlane is bound, if any.
// TODO: CHECK: what happens when it's bound to itself.
// pub fn parent<'a>(&'a mut self) -> Option<&'a mut NcPlane> {
pub fn parent<'a>(&'a mut self) -> &'a mut NcPlane {
unsafe { &mut *crate::ncplane_parent(self) }
}
/// Gets the parent to which this NcPlane is bound, if any.
// TODO: CHECK: what happens when it's bound to itself.
// pub fn parent<'a>(&'a mut self) -> Option<&'a mut NcPlane> {
pub fn parent_const<'a>(&'a self) -> &'a NcPlane {
unsafe { &*crate::ncplane_parent_const(self) }
}
// Context -----------------------------------------------------------------
/// Gets a mutable reference to the [Notcurses] context of this NcPlane.
pub fn notcurses<'a>(&mut self) -> &'a mut Notcurses {
unsafe { &mut *crate::ncplane_notcurses(self) }
}
/// Gets an immutable reference to the [Notcurses] context of this NcPlane.
pub fn notcurses_const<'a>(&mut self) -> &'a Notcurses {
unsafe { &*crate::ncplane_notcurses_const(self) }
}
}

@ -1,31 +1,32 @@
//! `NcPlane`
// functions already exported by bindgen : 105
// ------------------------------------------
// (#) 13 / 92 unit tests
// (W) 1 wrapped as a method or function
// ------------------------------------------
// ncpile_bottom
// # ncpile_create
// ncpile_rasterize
// ncpile_render
// ncpile_top
// ncplane_above
// ncplane_at_cursor
// ncplane_at_yx
// ncplane_base
// ncplane_below
// -------------------------------------------
// (X) wont: 4
// (#) test: 13
// (W) wrap: 28
// -------------------------------------------
//W ncpile_bottom
//W# ncpile_create
//W ncpile_rasterize
//W ncpile_render
//W ncpile_top
//W ncplane_above
//W ncplane_at_cursor
//W ncplane_at_yx
//W ncplane_base
//W ncplane_below
// ncplane_box
// ncplane_center_abs
// # ncplane_channels
// ncplane_contents
// ncplane_create
// # ncplane_cursor_move_yx
//W# ncplane_channels
//W ncplane_contents
//W ncplane_create
//W# ncplane_cursor_move_yx
//W# ncplane_cursor_yx
// ncplane_destroy
//W ncplane_destroy
//W# ncplane_dim_yx
// ncplane_dup
// # ncplane_erase
//W ncplane_dup
//W# ncplane_erase
// ncplane_fadein
// ncplane_fadein_iteration
// ncplane_fadeout
@ -36,41 +37,41 @@
// ncplane_highgradient
// ncplane_highgradient_sized
// ncplane_hline_interp
// # ncplane_home
//W# ncplane_home
// ncplane_mergedown
// ncplane_mergedown_simple
// ncplane_move_above
// ncplane_move_below
// ncplane_move_bottom
// ncplane_move_top
// ncplane_move_yx
//W ncplane_move_yx
// ncplane_new
// # ncplane_notcurses
// # ncplane_notcurses_const
//W# ncplane_notcurses
//W# ncplane_notcurses_const
// ncplane_off_styles
// ncplane_on_styles
// ncplane_parent
// ncplane_parent_const
//W ncplane_parent
//W ncplane_parent_const
// ncplane_polyfill_yx
// ncplane_pulse
// ncplane_putchar_stained
// ncplane_putc_yx
// ncplane_putegc_stained
// ncplane_putegc_yx
// X ncplane_putegc_stained
// X ncplane_putegc_yx
// ncplane_putnstr_aligned
// ncplane_putnstr_yx
// ncplane_putstr_aligned
// ncplane_putstr_stained
// ncplane_putstr_yx
// ncplane_puttext
// ncplane_putwegc_stained
// ncplane_putwstr_stained
// X ncplane_putwegc_stained
// X ncplane_putwstr_stained
// ncplane_qrcode
// ncplane_reparent
// ncplane_reparent_family
// # ncplane_resize
// ncplane_resizecb
// ncplane_resize_realign
//W# ncplane_resize
//W ncplane_resizecb
//W ncplane_resize_realign
// ncplane_rgba
// ncplane_rotate_ccw
// ncplane_rotate_cw
@ -113,12 +114,13 @@
//
// functions manually reimplemented: 42
// ------------------------------------------
// (X) wont: 8
// (X) wont: 9
// (+) done: 34 / 0
// (#) test: 5 / 29
// (W) wrap: 5
// (#) test: 5
// ------------------------------------------
// + ncplane_align
// + ncplane_at_cursor_cell
//W+ ncplane_at_cursor_cell
// + ncplane_at_yx_cell
// + ncplane_bchannel
// + ncplane_bg_alpha
@ -143,7 +145,7 @@
// + ncplane_putc
// + ncplane_putchar
// + ncplane_putchar_yx
// + ncplane_putegc
// X ncplane_putegc
// + ncplane_putnstr
//W+ ncplane_putstr
// X ncplane_putwc // I don't think these will be needed from Rust. See:
@ -154,7 +156,7 @@
// X ncplane_putwstr //
// X ncplane_putwstr_aligned //
// X ncplane_putwstr_yx //
// # ncplane_resize_simple
//W# ncplane_resize_simple
// + ncplane_rounded_box
// + ncplane_rounded_box_sized
// + ncplane_vline

@ -9,10 +9,10 @@ use crate::{
channels_fg_alpha, channels_fg_default_p, channels_fg_rgb, channels_fg_rgb8, cstring,
ffi::__va_list_tag, ncplane_at_cursor, ncplane_at_yx, ncplane_box, ncplane_channels,
ncplane_cursor_move_yx, ncplane_cursor_yx, ncplane_dim_yx, ncplane_gradient,
ncplane_hline_interp, ncplane_putc_yx, ncplane_putegc_yx, ncplane_putnstr_yx,
ncplane_putstr_yx, ncplane_resize, ncplane_styles, ncplane_vline_interp, ncplane_vprintf_yx,
notcurses_align, NcAlign, NcAlphaBits, NcCell, NcChannel, NcChannelPair, NcColor, NcPlane,
NcResult, NcStyleMask, NCRESULT_ERR, NCRESULT_OK,
ncplane_hline_interp, ncplane_putc_yx, ncplane_putnstr_yx, ncplane_putstr_yx, ncplane_resize,
ncplane_styles, ncplane_vline_interp, ncplane_vprintf_yx, notcurses_align, NcAlign,
NcAlphaBits, NcCell, NcChannel, NcChannelPair, NcColor, NcDimension, NcPlane, NcResult, NcRgb,
NcStyleMask, NCRESULT_ERR, NCRESULT_OK,
};
// Alpha -----------------------------------------------------------------------
@ -71,13 +71,13 @@ pub fn ncplane_bg_rgb8(
/// Gets the foreground [NcRgb] from an [NcPlane], shifted to LSBs.
#[inline]
pub fn ncplane_fg_rgb(plane: &NcPlane) -> NcChannel {
pub fn ncplane_fg_rgb(plane: &NcPlane) -> NcRgb {
channels_fg_rgb(unsafe { ncplane_channels(plane) })
}
/// Gets the background [NcRgb] from an [NcPlane], shifted to LSBs.
#[inline]
pub fn ncplane_bg_rgb(plane: &NcPlane) -> NcChannel {
pub fn ncplane_bg_rgb(plane: &NcPlane) -> NcRgb {
channels_bg_rgb(unsafe { ncplane_channels(plane) })
}
@ -105,32 +105,29 @@ pub fn ncplane_putc(plane: &mut NcPlane, cell: &NcCell) -> NcResult {
/// Calls ncplane_putchar_yx() at the current cursor location.
#[inline]
pub fn ncplane_putchar(plane: &mut NcPlane, c: char) -> NcResult {
ncplane_putchar_yx(plane, -1, -1, c)
}
/// Replaces the [NcEgc] underneath us, but retain the styling.
/// The current styling of the plane will not be changed.
///
/// Replace the [NcCell] at the specified coordinates with the provided 7-bit char.
///
/// Advance the cursor by 1. On success, returns 1. On failure, returns -1.
/// This works whether the underlying char is signed or unsigned.
#[inline]
pub fn ncplane_putchar_yx(plane: &mut NcPlane, y: i32, x: i32, ch: char) -> NcResult {
pub fn ncplane_putchar(plane: &mut NcPlane, ch: char) -> NcResult {
unsafe {
let cell = NcCell::with_all(ch, 0, ncplane_styles(plane), ncplane_channels(plane));
ncplane_putc_yx(plane, y, x, &cell)
ncplane_putc_yx(plane, -1, -1, &cell)
}
}
/// Calls `ncplane_putegc()` at the current cursor location.
/// Replaces the [NcCell] at the specified coordinates with the provided char.
/// Advances the cursor by 1.
#[inline]
pub fn ncplane_putegc(plane: &mut NcPlane, gcluster: i8, sbytes: &mut i32) -> NcResult {
unsafe { ncplane_putegc_yx(plane, -1, -1, &gcluster, sbytes) }
pub fn ncplane_putchar_yx(
plane: &mut NcPlane,
y: NcDimension,
x: NcDimension,
ch: char,
) -> NcResult {
unsafe {
let cell = NcCell::with_all(ch, 0, ncplane_styles(plane), ncplane_channels(plane));
ncplane_putc_yx(plane, y as i32, x as i32, &cell)
}
}
///
/// Writes a series of [NcEgc][crate::NcEgc]s to the current location, using the current style.
#[inline]
pub fn ncplane_putstr(plane: &mut NcPlane, string: &str) -> NcResult {
unsafe { ncplane_putstr_yx(plane, -1, -1, cstring![string]) }
@ -171,8 +168,21 @@ pub fn ncplane_at_cursor_cell(plane: &mut NcPlane, cell: &mut NcCell) -> NcResul
/// Retrieves the current contents of the specified cell into 'cell'.
/// This cell is invalidated if the associated plane is destroyed.
#[inline]
pub fn ncplane_at_yx_cell(plane: &mut NcPlane, y: i32, x: i32, cell: &mut NcCell) -> NcResult {
let mut egc = unsafe { ncplane_at_yx(plane, y, x, &mut cell.stylemask, &mut cell.channels) };
pub fn ncplane_at_yx_cell(
plane: &mut NcPlane,
y: NcDimension,
x: NcDimension,
cell: &mut NcCell,
) -> NcResult {
let mut egc = unsafe {
ncplane_at_yx(
plane,
y as i32,
x as i32,
&mut cell.stylemask,
&mut cell.channels,
)
};
if egc.is_null() {
return NCRESULT_ERR;
}
@ -189,74 +199,99 @@ pub fn ncplane_at_yx_cell(plane: &mut NcPlane, y: i32, x: i32, cell: &mut NcCell
/// Gets the columns of the [NcPlane].
#[inline]
pub fn ncplane_dim_x(plane: &NcPlane) -> i32 {
pub fn ncplane_dim_x(plane: &NcPlane) -> NcDimension {
unsafe {
let mut x = 0;
ncplane_dim_yx(plane, null_mut(), &mut x);
x
x as NcDimension
}
}
/// Gets the rows of the [NcPlane].
#[inline]
#[inline]
pub fn ncplane_dim_y(plane: &NcPlane) -> i32 {
pub fn ncplane_dim_y(plane: &NcPlane) -> NcDimension {
unsafe {
let mut y = 0;
ncplane_dim_yx(plane, &mut y, null_mut());
y
y as NcDimension
}
}
/// Resizes the plane, retaining what data we can (everything, unless we're
/// shrinking in some dimension). Keep the origin where it is.
#[inline]
pub fn ncplane_resize_simple(plane: &mut NcPlane, ylen: i32, xlen: i32) -> NcResult {
let (mut oldy, mut oldx) = (0, 0);
pub fn ncplane_resize_simple(
plane: &mut NcPlane,
y_len: NcDimension,
x_len: NcDimension,
) -> NcResult {
let (mut old_y, mut old_x) = (0, 0);
unsafe {
ncplane_dim_yx(plane, &mut oldy, &mut oldx);
ncplane_dim_yx(plane, &mut old_y, &mut old_x);
}
let keepleny = {
if oldy > ylen {
ylen
let keep_len_y = {
if old_y > y_len as i32 {
y_len as i32
} else {
oldy
old_y
}
};
let keeplenx = {
if oldx > xlen {
xlen
let keep_len_x = {
if old_x > x_len as i32 {
x_len as i32
} else {
oldx
old_x
}
};
unsafe { ncplane_resize(plane, 0, 0, keepleny, keeplenx, 0, 0, ylen, xlen) }
unsafe {
ncplane_resize(
plane,
0,
0,
keep_len_y,
keep_len_x,
0,
0,
y_len as i32,
x_len as i32,
)
}
}
/// Returns the column at which 'cols' columns ought start in order to be aligned
/// according to 'align' within ncplane 'n'. Returns INT_MAX on invalid 'align'.
/// Undefined behavior on negative 'cols'.
// NOTE: Leave cols as i32. See:
// - > https://github.com/dankamongmen/notcurses/issues/920
// - https://github.com/dankamongmen/notcurses/issues/904
#[inline]
pub fn ncplane_align(plane: &NcPlane, align: NcAlign, cols: i32) -> i32 {
pub fn ncplane_align(plane: &NcPlane, align: NcAlign, cols: NcDimension) -> NcResult {
notcurses_align(ncplane_dim_x(plane), align, cols)
}
// line ------------------------------------------------------------------------
/// On error, return the negative number of cells drawn.
/// Draws horizontal lines using the specified NcCell, starting at the current
/// cursor position.
///
/// The cursor will end at the cell following the last cell output,
/// just as if ncplane_putc() was called at that spot.
///
/// Returns the number of cells drawn on success. On error, returns the negative
/// number of cells drawn.
#[inline]
pub fn ncplane_hline(plane: &mut NcPlane, cell: &NcCell, len: i32) -> i32 {
unsafe { ncplane_hline_interp(plane, cell, len, cell.channels, cell.channels) }
pub fn ncplane_hline(plane: &mut NcPlane, cell: &NcCell, len: NcDimension) -> NcResult {
unsafe { ncplane_hline_interp(plane, cell, len as i32, cell.channels, cell.channels) }
}
/// Draws vertical lines using the specified NcCell, starting at the current
/// cursor position.
///
/// On error, return the negative number of cells drawn.
/// The cursor will end at the cell following the last cell output,
/// just as if ncplane_putc() was called at that spot.
///
/// Returns the number of cells drawn on success. On error, returns the negative
/// number of cells drawn.
#[inline]
pub fn ncplane_vline(plane: &mut NcPlane, cell: &NcCell, len: i32) -> i32 {
unsafe { ncplane_vline_interp(plane, cell, len, cell.channels, cell.channels) }
pub fn ncplane_vline(plane: &mut NcPlane, cell: &NcCell, len: NcDimension) -> NcResult {
unsafe { ncplane_vline_interp(plane, cell, len as i32, cell.channels, cell.channels) }
}
// perimeter -------------------------------------------------------------------
@ -277,7 +312,18 @@ pub fn ncplane_perimeter(
ncplane_cursor_move_yx(plane, 0, 0);
let (mut dimy, mut dimx) = (0, 0);
ncplane_dim_yx(plane, &mut dimy, &mut dimx);
ncplane_box_sized(plane, ul, ur, ll, lr, hline, vline, dimy, dimx, ctlword)
ncplane_box_sized(
plane,
ul,
ur,
ll,
lr,
hline,
vline,
dimy as NcDimension,
dimx as NcDimension,
ctlword,
)
}
}
@ -318,7 +364,18 @@ pub fn ncplane_perimeter_double(
{
return NCRESULT_ERR;
}
let ret = ncplane_box_sized(plane, &ul, &ur, &ll, &lr, &hl, &vl, dimy, dimx, ctlword);
let ret = ncplane_box_sized(
plane,
&ul,
&ur,
&ll,
&lr,
&hl,
&vl,
dimy as NcDimension,
dimx as NcDimension,
ctlword,
);
unsafe {
cell_release(plane, &mut ul);
cell_release(plane, &mut ur);
@ -367,7 +424,18 @@ pub fn ncplane_perimeter_rounded(
{
return NCRESULT_ERR;
}
let ret = ncplane_box_sized(plane, &ul, &ur, &ll, &lr, &hl, &vl, dimy, dimx, ctlword);
let ret = ncplane_box_sized(
plane,
&ul,
&ur,
&ll,
&lr,
&hl,
&vl,
dimy as NcDimension,
dimx as NcDimension,
ctlword,
);
unsafe {
cell_release(plane, &mut ul);
cell_release(plane, &mut ur);
@ -381,8 +449,8 @@ pub fn ncplane_perimeter_rounded(
// box -------------------------------------------------------------------------
/// Draw a box with its upper-left corner at the current cursor position, having
/// dimensions 'ylen'x'xlen'. See ncplane_box() for more information. The
/// Draws a box with its upper-left corner at the current cursor position,
/// having dimensions 'y_len' * 'x_len'. See ncplane_box() for more information. The
/// minimum box size is 2x2, and it cannot be drawn off-screen.
#[inline]
pub fn ncplane_box_sized(
@ -393,8 +461,8 @@ pub fn ncplane_box_sized(
lr: &NcCell,
hline: &NcCell,
vline: &NcCell,
ylen: i32,
xlen: i32,
y_len: NcDimension,
x_len: NcDimension,
ctlword: u32,
) -> NcResult {
let (mut y, mut x) = (0, 0);
@ -408,8 +476,8 @@ pub fn ncplane_box_sized(
lr,
hline,
vline,
y + ylen - 1,
x + xlen - 1,
y + y_len as i32 - 1,
x + x_len as i32 - 1,
ctlword,
)
}
@ -421,8 +489,8 @@ pub fn ncplane_double_box(
plane: &mut NcPlane,
stylemask: NcStyleMask,
channels: NcChannelPair,
ystop: i32,
xstop: i32,
ystop: NcDimension,
xstop: NcDimension,
ctlword: u32,
) -> NcResult {
#[allow(unused_assignments)]
@ -448,7 +516,18 @@ pub fn ncplane_double_box(
&mut vl,
);
if ret == NCRESULT_OK {
ret = ncplane_box(plane, &ul, &ur, &ll, &lr, &hl, &vl, ystop, xstop, ctlword);
ret = ncplane_box(
plane,
&ul,
&ur,
&ll,
&lr,
&hl,
&vl,
ystop as i32,
xstop as i32,
ctlword,
);
}
cell_release(plane, &mut ul);
@ -467,8 +546,8 @@ pub fn ncplane_double_box_sized(
plane: &mut NcPlane,
stylemask: NcStyleMask,
channels: NcChannelPair,
ylen: i32,
xlen: i32,
y_len: NcDimension,
x_len: NcDimension,
ctlword: u32,
) -> NcResult {
let (mut y, mut x) = (0, 0);
@ -479,8 +558,8 @@ pub fn ncplane_double_box_sized(
plane,
stylemask,
channels,
y + ylen - 1,
x + xlen - 1,
y as NcDimension + y_len - 1,
x as NcDimension + x_len - 1,
ctlword,
)
}
@ -491,8 +570,8 @@ pub fn ncplane_rounded_box(
plane: &mut NcPlane,
stylemask: NcStyleMask,
channels: NcChannelPair,
ystop: i32,
xstop: i32,
ystop: NcDimension,
xstop: NcDimension,
ctlword: u32,
) -> NcResult {
#[allow(unused_assignments)]
@ -518,7 +597,18 @@ pub fn ncplane_rounded_box(
&mut vl,
);
if ret == NCRESULT_OK {
ret = ncplane_box(plane, &ul, &ur, &ll, &lr, &hl, &vl, ystop, xstop, ctlword);
ret = ncplane_box(
plane,
&ul,
&ur,
&ll,
&lr,
&hl,
&vl,
ystop as i32,
xstop as i32,
ctlword,
);
}
cell_release(plane, &mut ul);
cell_release(plane, &mut ur);
@ -536,8 +626,8 @@ pub fn ncplane_rounded_box_sized(
plane: &mut NcPlane,
stylemask: NcStyleMask,
channels: NcChannelPair,
ylen: i32,
xlen: i32,
y_len: NcDimension,
x_len: NcDimension,
ctlword: u32,
) -> NcResult {
let (mut y, mut x) = (0, 0);
@ -548,8 +638,8 @@ pub fn ncplane_rounded_box_sized(
plane,
stylemask,
channels,
y + ylen - 1,
x + xlen - 1,
y as NcDimension + y_len - 1,
x as NcDimension + x_len - 1,
ctlword,
)
}
@ -557,7 +647,7 @@ pub fn ncplane_rounded_box_sized(
// gradient --------------------------------------------------------------------
/// Draw a gradient with its upper-left corner at the current cursor position,
/// having dimensions 'ylen'x'xlen'. See ncplane_gradient for more information.
/// having dimensions 'y_len' * 'x_len'. See ncplane_gradient for more information.
/// static inline int
// XXX receive cells as u32? See:
// - https://github.com/dankamongmen/notcurses/issues/920
@ -571,10 +661,10 @@ pub fn ncplane_gradient_sized(
ur: u64,
ll: u64,
lr: u64,
ylen: i32,
xlen: i32,
y_len: NcDimension,
x_len: NcDimension,
) -> NcResult {
if ylen < 1 || xlen < 1 {
if y_len < 1 || x_len < 1 {
return NCRESULT_ERR;
}
let (mut y, mut x) = (0, 0);
@ -588,8 +678,8 @@ pub fn ncplane_gradient_sized(
ur,
ll,
lr,
y + ylen - 1,
x + xlen - 1,
y + y_len as i32 - 1,
x + x_len as i32 - 1,
)
}
}

@ -1,6 +1,6 @@
//! `NcTime`
///
// only used for now with [`notcurses_getc_nblock`], which can't use
// Expected by [`notcurses_getc`] & [`notcurses_getc_nblock`], that can't use
// libc::timespec
pub type NcTime = crate::bindings::ffi::timespec;

Loading…
Cancel
Save