libnotcurses_sys/notcurses/
methods.rs

1//! `Nc*` methods and associated functions.
2
3use core::ptr::{null, null_mut};
4
5use crate::{
6    c_api::{self, notcurses_init},
7    cstring, error, error_ref_mut, rstring, rstring_free, Nc, NcAlign, NcBlitter, NcCapabilities,
8    NcChannels, NcError, NcFd, NcFlag, NcInput, NcLogLevel, NcMiceEvents, NcOptions, NcPixelImpl,
9    NcPlane, NcReceived, NcResult, NcRgb, NcScale, NcStats, NcStyle, NcTime, NcVisual,
10    NcVisualGeometry, NcVisualOptions,
11};
12
13#[cfg(not(feature = "std"))]
14use alloc::{
15    format,
16    string::{String, ToString},
17    vec::Vec,
18};
19
20#[cfg(feature = "std")]
21use crate::NcFile;
22
23/// # `Nc` Constructors and destructors
24impl Nc {
25    /// New notcurses context.
26    ///
27    /// Has the [`SuppressBanners`] flag enabled.
28    ///
29    /// # Safety
30    /// You can't have multiple simultaneous `Nc` instances in the same thread.
31    ///
32    /// [`SuppressBanners`]: NcFlag#associatedconstant.SuppressBanners
33    pub unsafe fn new<'a>() -> NcResult<&'a mut Nc> {
34        Self::with_flags(NcFlag::SuppressBanners)
35    }
36
37    /// New notcurses context in CLI mode.
38    ///
39    /// Has the [`CliMode`] and [`SuppressBanners`] flags enabled.
40    ///
41    /// [`CliMode`]: NcFlag#associatedconstant.CliMode
42    /// [`SuppressBanners`]: NcFlag#associatedconstant.SuppressBanners
43    ///
44    /// # Safety
45    /// You can't have multiple simultaneous `Nc` instances in the same thread.
46    pub unsafe fn new_cli<'a>() -> NcResult<&'a mut Nc> {
47        Self::with_flags(NcFlag::CliMode | NcFlag::SuppressBanners)
48    }
49
50    /// New notcurses context, with banners.
51    ///
52    /// It prints the version information banner at initialization
53    /// and the performance information banner at finalization.
54    ///
55    /// # Safety
56    /// You can't have multiple simultaneous `Nc` instances in the same thread.
57    pub unsafe fn with_banners<'a>() -> NcResult<&'a mut Nc> {
58        Self::with_flags(NcFlag::None)
59    }
60
61    /// New notcurses context in CLI mode, with banners.
62    ///
63    /// It prints the version information banner at initialization
64    /// and the performance information banner at finalization.
65    ///
66    /// It has the [`CliMode`] flag enabled.
67    ///
68    /// [`CliMode`]: NcFlag#associatedconstant.CliMode
69    ///
70    /// # Safety
71    /// You can't have multiple simultaneous `Nc` instances in the same thread.
72    pub unsafe fn with_banners_cli<'a>() -> NcResult<&'a mut Nc> {
73        Self::with_flags(NcFlag::CliMode)
74    }
75
76    /// New notcurses context, expecting `flags`.
77    ///
78    /// # Safety
79    /// You can't have multiple simultaneous `Nc` instances in the same thread.
80    pub unsafe fn with_flags<'a>(flags: impl Into<NcFlag>) -> NcResult<&'a mut Nc> {
81        Self::with_options(NcOptions::with_flags(flags.into()))
82    }
83
84    /// New notcurses context, expects `flags` and `log_level`.
85    ///
86    /// # Safety
87    /// You can't have multiple simultaneous `Nc` instances in the same thread.
88    pub unsafe fn with_flags_log<'a>(
89        flags: impl Into<NcFlag>,
90        log_level: impl Into<NcLogLevel>,
91    ) -> NcResult<&'a mut Nc> {
92        Self::with_options(NcOptions::with_all_options(
93            log_level.into(),
94            Some((0, 0, 0, 0)),
95            flags.into(),
96        ))
97    }
98
99    /// New notcurses context, expects [`NcOptions`].
100    ///
101    /// # Safety
102    /// You can't have multiple simultaneous `Nc` instances in the same thread.
103    pub unsafe fn with_options<'a>(options: NcOptions) -> NcResult<&'a mut Nc> {
104        let res = notcurses_init(&options, null_mut());
105        error_ref_mut![res, &format!["Nc.with_options({:?})", options]]
106    }
107
108    /// Destroys the notcurses context.
109    ///
110    /// # Safety
111    /// You must not call this method repeatedly on the same `Nc` instance.
112    ///
113    /// *C style function: [notcurses_stop()][c_api::notcurses_stop].*
114    pub unsafe fn stop(&mut self) -> NcResult<()> {
115        error![c_api::notcurses_stop(self)]
116    }
117
118    /// Destroys all [`NcPlane`]s other than the stdplane.
119    ///
120    /// # Safety
121    /// Must not use any pre-existing references to `NcPlane`s.
122    ///
123    /// *C style function: [notcurses_drop_planes()][c_api::notcurses_drop_planes].*
124    pub unsafe fn drop_planes(&mut self) {
125        c_api::notcurses_drop_planes(self);
126    }
127}
128
129/// # `Nc` methods
130impl Nc {
131    /// Returns the offset into `availcols` at which `cols` ought be output given
132    /// the requirements of `align`.
133    ///
134    /// Returns `-`[NCRESULT_MAX][c_api::NCRESULT_MAX] if
135    /// [`NcAlign::Unaligned`].
136    ///
137    /// *C style function: [notcurses_align()][c_api::notcurses_align].*
138    pub fn align(availcols: u32, align: impl Into<NcAlign>, cols: u32) -> NcResult<u32> {
139        let align = align.into();
140        let res = c_api::notcurses_align(availcols, align, cols);
141        error![res, &format!("Nc.align({:?}, {})", align, cols), res as u32]
142    }
143
144    /// Retrieves the current contents of the specified [`NcCell`][crate::NcCell]
145    /// as last rendered, returning the `EGC` (or None on error) and writing
146    /// out the [`NcStyle`] and the [`NcChannels`].
147    ///
148    /// *C style function: [notcurses_at_yx()][c_api::notcurses_at_yx].*
149    pub fn at_yx(
150        &mut self,
151        y: u32,
152        x: u32,
153        stylemask: &mut NcStyle,
154        channels: &mut NcChannels,
155    ) -> Option<String> {
156        let egc = unsafe { c_api::notcurses_at_yx(self, y, x, stylemask.into(), &mut channels.0) };
157        if egc.is_null() {
158            return None;
159        }
160        Some(rstring_free![egc])
161    }
162
163    /// Returns the detected capabilities of the current terminal.
164    ///
165    /// *C style function: [notcurses_capabilities()][c_api::notcurses_capabilities].*
166    pub fn capabilities(&self) -> NcCapabilities {
167        unsafe { *c_api::notcurses_capabilities(self) }
168    }
169
170    /// Returns true if we can reliably use Unicode Braille.
171    ///
172    /// See also [`NcBlitter::Braille`].
173    ///
174    /// *C style function: [notcurses_canbraille()][c_api::notcurses_canbraille].*
175    pub fn canbraille(&self) -> bool {
176        c_api::notcurses_canbraille(self)
177    }
178
179    /// Returns true if it's possible to set the "hardware" palette.
180    ///
181    /// Requires the "ccc" terminfo capability.
182    ///
183    /// *C style function: [notcurses_canchangecolor()][c_api::notcurses_canchangecolor].*
184    pub fn canchangecolor(&self) -> bool {
185        c_api::notcurses_canchangecolor(self)
186    }
187
188    /// Returns true if fading is possible.
189    ///
190    /// Fading requires either the "rgb" or "ccc" terminfo capability.
191    ///
192    /// *C style function: [notcurses_canfade()][c_api::notcurses_canfade].*
193    pub fn canfade(&self) -> bool {
194        c_api::notcurses_canfade(self)
195    }
196
197    /// Returns true if we can reliably use Unicode half blocks.
198    ///
199    /// See also [`NcBlitter::Half`].
200    ///
201    /// *C style function: [notcurses_canhalfblock()][c_api::notcurses_canhalfblock].*
202    pub fn canhalfblock(&self) -> bool {
203        c_api::notcurses_canhalfblock(self)
204    }
205
206    /// Returns true if loading images is possible.
207    ///
208    /// This requires being built against FFmpeg/OIIO.
209    ///
210    /// *C style function: [notcurses_canopen_images()][c_api::notcurses_canopen_images].*
211    pub fn canopen_images(&self) -> bool {
212        unsafe { c_api::notcurses_canopen_images(self) }
213    }
214
215    /// Returns true if loading videos is possible.
216    ///
217    /// This requires being built against FFmpeg.
218    ///
219    /// *C style function: [notcurses_canopen_videos()][c_api::notcurses_canopen_videos].*
220    pub fn canopen_videos(&self) -> bool {
221        unsafe { c_api::notcurses_canopen_videos(self) }
222    }
223
224    /// Returns true if we can blit pixel-accurate bitmaps.
225    ///
226    /// See also [`check_pixel_support`][Nc#method.check_pixel_support].
227    ///
228    /// *C style function: [notcurses_canpixel()][c_api::notcurses_canpixel].*
229    pub fn canpixel(&self) -> bool {
230        c_api::notcurses_canpixel(self)
231    }
232
233    /// Returns true if we can reliably use Unicode quadrant blocks.
234    ///
235    /// See also [`NcBlitter::Quadrant`].
236    ///
237    /// *C style function: [notcurses_canquadrant()][c_api::notcurses_canquadrant].*
238    pub fn canquadrant(&self) -> bool {
239        c_api::notcurses_canquadrant(self)
240    }
241
242    /// Returns true if we can reliably use Unicode 13 sextants.
243    ///
244    /// See also [`NcBlitter::Sextant`].
245    ///
246    /// *C style function: [notcurses_cansextant()][c_api::notcurses_cansextant].*
247    pub fn cansextant(&self) -> bool {
248        c_api::notcurses_cansextant(self)
249    }
250
251    /// Returns true if it's possible to directly specify RGB values per cell,
252    /// or false if it's only possible to use palettes.
253    ///
254    /// *C style function: [notcurses_cantruecolor()][c_api::notcurses_cantruecolor].*
255    pub fn cantruecolor(&self) -> bool {
256        c_api::notcurses_cantruecolor(self)
257    }
258
259    /// Returns true if the encoding is UTF-8.
260    ///
261    /// Requires `LANG` being set to a UTF-8 locale.
262    ///
263    /// *C style function: [notcurses_canutf8()][c_api::notcurses_canutf8].*
264    pub fn canutf8(&self) -> bool {
265        c_api::notcurses_canutf8(self)
266    }
267
268    /// Checks for pixel support.
269    ///
270    /// Returns [`NcPixelImpl`] with a non-zero constant corresponding to some
271    /// pixel-blitting mechanism if bitmap support (via any mechanism) has been
272    /// detected, or else 0 (NCPIXEL_NONE).
273    ///
274    /// *C style function: [notcurses_check_pixel_support()][c_api::notcurses_check_pixel_support].*
275    #[allow(clippy::wildcard_in_or_patterns)]
276    pub fn check_pixel_support(&self) -> NcPixelImpl {
277        unsafe { c_api::notcurses_check_pixel_support(self) }.into()
278    }
279
280    /// Returns the default foreground color, if it is known.
281    pub fn default_foreground(&self) -> Option<NcRgb> {
282        let mut fg = 0;
283        let res = unsafe { c_api::notcurses_default_foreground(self, &mut fg) };
284        if res == c_api::NCRESULT_ERR {
285            None
286        } else {
287            Some(fg.into())
288        }
289    }
290
291    /// Returns the default background color, if it is known.
292    pub fn default_background(&self) -> Option<NcRgb> {
293        let mut bg = 0;
294        let res = unsafe { c_api::notcurses_default_background(self, &mut bg) };
295        if res == c_api::NCRESULT_ERR {
296            None
297        } else {
298            Some(bg.into())
299        }
300    }
301
302    /// Disables the terminal's cursor, if supported.
303    ///
304    /// Immediate effect (no need for a call to notcurses_render()).
305    ///
306    /// *C style function: [notcurses_cursor_disable()][c_api::notcurses_cursor_disable].*
307    pub fn cursor_disable(&mut self) -> NcResult<()> {
308        error![unsafe { c_api::notcurses_cursor_disable(self) }]
309    }
310
311    /// Enables the terminal's cursor, if supported, placing it at `y`, `x`.
312    ///
313    /// Immediate effect (no need for a call to notcurses_render()).
314    /// It is an error if `y`, `x` lies outside the standard plane.
315    ///
316    /// *C style function: [notcurses_cursor_enable()][c_api::notcurses_cursor_enable].*
317    pub fn cursor_enable(&mut self, y: u32, x: u32) -> NcResult<()> {
318        error![unsafe { c_api::notcurses_cursor_enable(self, y as i32, x as i32) }]
319    }
320
321    /// Shifts to the alternate screen, if available.
322    ///
323    /// If already using the alternate screen, this returns Ok(()) immediately.
324    ///
325    /// If the alternate screen is not available, returns an Error immediately.
326    ///
327    /// Entering the alternate screen turns off scrolling for the standard plane.
328    ///
329    /// *C style function:
330    /// [notcurses_enter_alternate_screen()][c_api::notcurses_enter_alternate_screen].*
331    pub fn enter_alternate_screen(&mut self) -> NcResult<()> {
332        error![unsafe { c_api::notcurses_enter_alternate_screen(self) }]
333    }
334
335    /// Exits the alternate screen.
336    ///
337    /// Immediately returns Ok(()) if not currently using the alternate screen.
338    ///
339    /// *C style function:
340    /// [notcurses_leave_alternate_screen()][c_api::notcurses_leave_alternate_screen].*
341    pub fn leave_alternate_screen(&mut self) -> NcResult<()> {
342        error![unsafe { c_api::notcurses_leave_alternate_screen(self) }]
343    }
344
345    /// Dumps notcurses state to the supplied `debugfp`.
346    ///
347    /// Output is freeform, and subject to change. It includes geometry of all
348    /// planes, from all piles.
349    ///
350    /// *C style function: [notcurses_debug()][c_api::notcurses_debug].*
351    #[cfg(feature = "std")]
352    #[cfg_attr(feature = "nightly", doc(cfg(feature = "std")))]
353    pub fn debug(&mut self, debugfp: &mut NcFile) {
354        unsafe {
355            c_api::notcurses_debug(self, debugfp.as_nc_ptr());
356        }
357    }
358
359    /// Returns the name of the user under which we are running.
360    ///
361    /// *C style function: [notcurses_accountname()][c_api::notcurses_accountname].*
362    pub fn accountname() -> String {
363        rstring_free![c_api::notcurses_accountname()]
364    }
365
366    /// Returns the name of the local hostname.
367    ///
368    /// *C style function: [notcurses_hostname()][c_api::notcurses_hostname].*
369    pub fn hostname() -> String {
370        rstring_free![c_api::notcurses_hostname()]
371    }
372
373    /// Returns the name of the detected OS version.
374    ///
375    /// *C style function: [notcurses_osversion()][c_api::notcurses_osversion].*
376    pub fn osversion() -> String {
377        rstring_free![c_api::notcurses_osversion()]
378    }
379
380    /// Returns the name of the detected terminal.
381    ///
382    /// *C style function: [notcurses_detected_terminal()][c_api::notcurses_detected_terminal].*
383    pub fn detected_terminal(&self) -> String {
384        rstring_free![c_api::notcurses_detected_terminal(self)]
385    }
386
387    /// Reads input.
388    ///
389    /// Provide `None` in `time` to block at length, and otherwise
390    /// `Some(`[`NcTime`]`)` to bound blocking.
391    ///
392    /// `time` is an a delay bound against `CLOCK_MONOTONIC`
393    /// (see [*pthread_cond_clockwait(3)*](https://linux.die.net/man/3/pthread_cond_wait)).
394    ///
395    /// *C style function: [notcurses_get()][c_api::notcurses_get].*
396    pub fn get(
397        &mut self,
398        time: Option<NcTime>,
399        input: Option<&mut NcInput>,
400    ) -> NcResult<NcReceived> {
401        let ntime = if let Some(time) = time { &time as *const _ } else { null() };
402        let ninput = if let Some(input) = input { input as *mut _ } else { null_mut() };
403
404        let res = unsafe { c_api::notcurses_get(self, ntime, ninput) };
405        if res == c_api::NCRESULT_ERR as u32 {
406            Err(NcError::new_msg(&format!["Nc.get({:?})", time]))
407        } else {
408            Ok(NcReceived::from(res))
409        }
410    }
411
412    /// Reads input blocking until an event is processed or a signal is received.
413    ///
414    /// Will optionally write the event details in `input`.
415    ///
416    /// *C style function: [notcurses_get_blocking()][c_api::notcurses_get_blocking].*
417    pub fn get_blocking(&mut self, input: Option<&mut NcInput>) -> NcResult<NcReceived> {
418        let res = c_api::notcurses_get_blocking(self, input);
419        if res == c_api::NCRESULT_ERR {
420            Err(NcError::new_msg("Nc.get_blocking()"))
421        } else {
422            Ok(NcReceived::from(res as u32))
423        }
424    }
425
426    /// Reads input without blocking.
427    ///
428    /// *C style function: [notcurses_get_nblock()][c_api::notcurses_get_nblock].*
429    pub fn get_nblock(&mut self, input: Option<&mut NcInput>) -> NcResult<NcReceived> {
430        let res = c_api::notcurses_get_nblock(self, input);
431        if res == c_api::NCRESULT_ERR {
432            Err(NcError::new_msg("Nc.get_nblock()"))
433        } else {
434            Ok(NcReceived::from(res as u32))
435        }
436    }
437
438    /// Acquire up to 'vcount' [`NcInput`]s at the vector 'ni'.
439    ///
440    /// The number read will be returned, or 0 on timeout.
441    ///
442    /// *C style function: [notcurses_getvec()][c_api::notcurses_getvec].*
443    pub fn getvec(
444        &mut self,
445        time: Option<NcTime>,
446        ni: &mut Vec<NcInput>,
447        vcount: u32,
448    ) -> NcResult<u32> {
449        let ntime = if let Some(time) = time { &time as *const _ } else { null() };
450        let nivec = ni.as_mut_ptr() as *mut NcInput;
451
452        let res = unsafe { c_api::notcurses_getvec(self, ntime, nivec, vcount as i32) };
453        error![res, "", res as u32]
454    }
455
456    /// Gets a file descriptor suitable for input event poll()ing.
457    ///
458    /// When this descriptor becomes available, you can call
459    /// [get_nblock()][Nc#method.get_nblock], and input ought be ready.
460    ///
461    /// This file descriptor is not necessarily the file descriptor associated
462    /// with stdin (but it might be!).
463    ///
464    /// *C style function: [notcurses_inputready_fd()][c_api::notcurses_inputready_fd].*
465    pub fn inputready_fd(&mut self) -> NcResult<NcFd> {
466        let res = unsafe { c_api::notcurses_inputready_fd(self) };
467        error![res, "", res]
468    }
469
470    /// Returns an [`NcBlitter`] from a string representation.
471    ///
472    /// *C style function: [notcurses_lex_blitter()][c_api::notcurses_lex_blitter].*
473    pub fn lex_blitter(blitter_str: &str) -> NcResult<NcBlitter> {
474        let mut blitter = 0;
475        let cs = cstring![blitter_str];
476        error![
477            unsafe { c_api::notcurses_lex_blitter(cs.as_ptr(), &mut blitter) },
478            "Invalid blitter name",
479            blitter.into()
480        ]
481    }
482
483    /// Lexes a margin argument according to the standard notcurses definition.
484    ///
485    /// There can be either a single number, which will define all margins equally,
486    /// or there can be four numbers separated by commas.
487    ///
488    /// *C style function: [notcurses_lex_margins()][c_api::notcurses_lex_margins].*
489    pub fn lex_margins(margins_str: &str, options: &mut NcOptions) -> NcResult<()> {
490        let cs = cstring![margins_str];
491        error![unsafe { c_api::notcurses_lex_margins(cs.as_ptr(), options) }]
492    }
493
494    /// Returns an [`NcScale`] from a string representation.
495    ///
496    /// *C style function: [notcurses_lex_scalemode()][c_api::notcurses_lex_scalemode].*
497    pub fn lex_scalemode(scale_str: &str) -> NcResult<NcScale> {
498        let mut scale = 0;
499        let cs = cstring![scale_str];
500        error![
501            unsafe { c_api::notcurses_lex_scalemode(cs.as_ptr(), &mut scale) },
502            "",
503            scale.into()
504        ]
505    }
506
507    /// Returns an [`NcStyle`] from a string representation.
508    ///
509    /// It is case-insensitive, and supports multiple styles separated by
510    /// spaces.
511    ///
512    /// The supported styles are: `italic`, `underline`, `undercurl`,
513    /// `struck`, `bold`, and `none`.
514    ///
515    /// If a style is are not recognized returns an error.
516    ///
517    /// *(No equivalent C style function)*
518    pub fn lex_styles(styles_str: &str) -> NcResult<NcStyle> {
519        let mut style = NcStyle::None;
520        let mut errstr = String::new();
521
522        for s in styles_str.split(' ') {
523            match s.to_lowercase().as_str() {
524                "italic" => style.set(NcStyle::Italic),
525                "underline" => style.set(NcStyle::Underline),
526                "undercurl" => style.set(NcStyle::Undercurl),
527                "struck" => style.set(NcStyle::Struck),
528                "bold" => style.set(NcStyle::Bold),
529                "none" => (),
530                _ => {
531                    errstr.push_str(s);
532                    errstr.push(' ');
533                }
534            }
535        }
536        if errstr.is_empty() {
537            Ok(style)
538        } else {
539            let _ = errstr.pop();
540            Err(NcError::new_msg(&format![
541                "the following styles are not recognized: '{}'",
542                errstr
543            ]))
544        }
545    }
546
547    /// Disables signals originating from the terminal's line discipline, i.e.
548    /// SIGINT (^C), SIGQUIT (^), and SIGTSTP (^Z). They are enabled by default.
549    ///
550    /// *C style function: [notcurses_linesigs_disable()][c_api::notcurses_linesigs_disable].*
551    pub fn linesigs_disable(&mut self) -> NcResult<()> {
552        error![unsafe { c_api::notcurses_linesigs_disable(self) }]
553    }
554
555    /// Restores signals originating from the terminal's line discipline, i.e.
556    /// SIGINT (^C), SIGQUIT (^), and SIGTSTP (^Z), if disabled.
557    ///
558    /// *C style function: [notcurses_linesigs_enable()][c_api::notcurses_linesigs_enable].*
559    pub fn linesigs_enable(&mut self) -> NcResult<()> {
560        error![unsafe { c_api::notcurses_linesigs_enable(self) }]
561    }
562
563    /// Disables mice events.
564    ///
565    /// *C style function: [notcurses_mice_disable()][c_api::notcurses_mice_disable].*
566    pub fn mice_disable(&mut self) -> NcResult<()> {
567        self.mice_enable(NcMiceEvents::None)
568    }
569
570    /// Enables mice events according to `eventmask`.
571    ///
572    /// An eventmask of 0 will disable all mice tracking.
573    ///
574    /// On success mouse events will be published to `notcurses_get`.
575    ///
576    /// *C style function: [notcurses_mice_enable()][c_api::notcurses_mice_enable].*
577    pub fn mice_enable(&mut self, eventmask: NcMiceEvents) -> NcResult<()> {
578        error![
579            unsafe { c_api::notcurses_mice_enable(self, eventmask.into()) },
580            "Nc.mice_enable()"
581        ]
582    }
583
584    /// Returns the number of simultaneous colors claimed to be supported,
585    /// if there is color support.
586    ///
587    /// Note that several terminal emulators advertise more colors than they
588    /// actually support, downsampling internally.
589    ///
590    /// *C style function: [notcurses_palette_size()][c_api::notcurses_palette_size].*
591    pub fn palette_size(&self) -> NcResult<u32> {
592        let res = unsafe { c_api::notcurses_palette_size(self) };
593        if res == 1 {
594            return Err(NcError::with_msg(1, "No color support ← Nc.palette_size()"));
595        }
596        Ok(res)
597    }
598
599    /// Refreshes the physical screen to match what was last rendered (i.e.,
600    /// without reflecting any changes since the last call to
601    /// [`render`][crate::Nc#method.render]).
602    ///
603    /// Returns the current screen geometry (`y`, `x`).
604    ///
605    /// This is primarily useful if the screen is externally corrupted, or if an
606    /// [NcKey::Resize][crate::NcKey#associatedconstant.Resize] event
607    /// has been read and you're not yet ready to render.
608    ///
609    /// *C style function: [notcurses_refresh()][c_api::notcurses_refresh].*
610    pub fn refresh(&mut self) -> NcResult<(u32, u32)> {
611        let (mut y, mut x) = (0, 0);
612        error![
613            unsafe { c_api::notcurses_refresh(self, &mut y, &mut x) },
614            "",
615            (y, x)
616        ]
617    }
618
619    /// Renders and rasterizes the standard pile in one shot. Blocking call.
620    ///
621    /// *C style function: [notcurses_render()][c_api::notcurses_render].*
622    pub fn render(&mut self) -> NcResult<()> {
623        error![c_api::notcurses_render(self), "Nc.render()"]
624    }
625
626    /// Acquires an atomic snapshot of the notcurses object's stats.
627    ///
628    /// *C style function: [notcurses_stats()][c_api::notcurses_stats].*
629    pub fn stats(&mut self, stats: &mut NcStats) {
630        unsafe {
631            c_api::notcurses_stats(self, stats);
632        }
633    }
634
635    /// Allocates an [`NcStats`] object.
636    ///
637    /// Use this rather than allocating your own, since future versions of
638    /// notcurses might enlarge this structure.
639    ///
640    /// *C style function: [notcurses_stats_alloc()][c_api::notcurses_stats_alloc].*
641    pub fn stats_alloc(&mut self) -> &mut NcStats {
642        unsafe { &mut *c_api::notcurses_stats_alloc(self) }
643    }
644
645    /// Resets all cumulative stats (immediate ones, such as fbbytes, are not reset).
646    ///
647    /// *C style function: [notcurses_stats_reset()][c_api::notcurses_stats_reset].*
648    pub fn stats_reset(&mut self, stats: &mut NcStats) {
649        unsafe {
650            c_api::notcurses_stats_reset(self, stats);
651        }
652    }
653
654    // TODO: decide what to do with these two:
655    //
656    // /// [notcurses_stdplane()][c_api::notcurses_stdplane], plus free bonus
657    // /// dimensions written to non-NULL y/x!
658    // ///
659    // /// *C style function: [notcurses_stddim_yx()][c_api::notcurses_stddim_yx].*
660    // #[inline]
661    // pub fn stddim_yx<'a>(
662    //     &'a mut self,
663    //     y: &mut u32,
664    //     x: &mut u32,
665    // ) -> NcResult<&'a mut NcPlane> {
666    //     c_api::notcurses_stddim_yx(self, y, x)
667    // }
668
669    // /// [stdplane_const()][Nc#method.stdplane_const], plus free
670    // /// bonus dimensions written to non-NULL y/x!
671    // ///
672    // /// *C style function: [notcurses_stddim_yx()][c_api::notcurses_stddim_yx].*
673    // #[inline]
674    // pub fn stddim_yx_const<'a>(
675    //     &'a self,
676    //     y: &mut u32,
677    //     x: &mut u32,
678    // ) -> NcResult<&'a NcPlane> {
679    //     c_api::notcurses_stddim_yx_const(self, y, x)
680    // }
681
682    /// Returns a mutable reference to the standard [`NcPlane`] for this terminal.
683    ///
684    /// The standard plane always exists, and its origin is always at the
685    /// uppermost, leftmost cell.
686    ///
687    /// # Safety
688    /// You must be careful not to end up with multiple exclusive references
689    /// to the standard plane, or with one exclusive reference and one or more
690    /// shared references.
691    ///
692    /// *C style function: [notcurses_stdplane()][c_api::notcurses_stdplane].*
693    pub unsafe fn stdplane<'a>(&mut self) -> &'a mut NcPlane {
694        &mut *c_api::notcurses_stdplane(self)
695    }
696
697    /// Returns a reference to the standard [`NcPlane`] for this terminal.
698    ///
699    /// The standard plane always exists, and its origin is always at the
700    /// uppermost, leftmost cell.
701    ///
702    /// # Safety
703    /// You must be careful not to end up with a mix of exclusive references
704    /// and shared references to the standard plane.
705    ///
706    /// *C style function: [notcurses_stdplane_const()][c_api::notcurses_stdplane_const].*
707    pub unsafe fn stdplane_const<'a>(&self) -> &'a NcPlane {
708        &*c_api::notcurses_stdplane_const(self)
709    }
710
711    /// Gets the name of an [`NcBlitter`] blitter.
712    ///
713    /// *C style function: [notcurses_str_blitter()][c_api::notcurses_str_blitter].*
714    pub fn str_blitter(blitter: impl Into<NcBlitter>) -> String {
715        rstring![c_api::notcurses_str_blitter(blitter.into().into())].to_string()
716    }
717
718    /// Gets the name of an [`NcScale`] scaling mode.
719    ///
720    /// *C style function: [notcurses_str_scalemode()][c_api::notcurses_str_scalemode].*
721    pub fn str_scalemode(scale: impl Into<NcScale>) -> String {
722        rstring![c_api::notcurses_str_scalemode(scale.into().into())].to_string()
723    }
724
725    /// Gets the lowercase name (or names) of the styles included in an [`NcStyle`].
726    ///
727    /// *(No equivalent C style function)*
728    pub fn str_styles(style: impl Into<NcStyle>) -> String {
729        let mut string = String::new();
730
731        let styles = style.into();
732        for s in styles.to_vec() {
733            string.push_str(match s {
734                NcStyle::Italic => "Italic ",
735                NcStyle::Underline => "Underline ",
736                NcStyle::Undercurl => "Undercurl ",
737                NcStyle::Struck => "Struck ",
738                NcStyle::Bold => "Bold ",
739                NcStyle::None => "None ",
740                _ => "",
741            });
742        }
743        let _ = string.pop();
744        string
745    }
746
747    /// Returns an [`NcStyle`] with the supported curses-style attributes.
748    ///
749    /// The attribute is only indicated as supported if the terminal can support
750    /// it together with color.
751    ///
752    /// For more information, see the "ncv" capability in terminfo(5).
753    ///
754    /// *C style function: [notcurses_supported_styles()][c_api::notcurses_supported_styles].*
755    pub fn supported_styles(&self) -> NcStyle {
756        unsafe { c_api::notcurses_supported_styles(self).into() }
757    }
758
759    /// Returns our current idea of the terminal dimensions in rows and cols.
760    ///
761    /// *C style function: [notcurses_term_dim_yx()][c_api::notcurses_term_dim_yx].*
762    pub fn term_dim_yx(&self) -> (u32, u32) {
763        c_api::notcurses_term_dim_yx(self)
764    }
765
766    /// Returns the bottommost [`NcPlane`] on the standard pile,
767    /// of which there is always at least one.
768    ///
769    /// *C style function: [notcurses_bottom()][c_api::notcurses_bottom].*
770    pub fn bottom(&mut self) -> &mut NcPlane {
771        c_api::notcurses_bottom(self)
772    }
773
774    /// Returns the topmost [`NcPlane`], of which there is always at least one.
775    ///
776    /// *C style function: [notcurses_top()][c_api::notcurses_top].*
777    pub fn top(&mut self) -> &mut NcPlane {
778        c_api::notcurses_top(self)
779    }
780
781    /// Returns a human-readable string describing the running notcurses version.
782    ///
783    /// *C style function: [notcurses_version()][c_api::notcurses_version].*
784    pub fn version() -> String {
785        rstring![c_api::notcurses_version()].to_string()
786    }
787
788    /// Returns the running notcurses version components
789    /// (major, minor, patch, tweak).
790    ///
791    /// *C style function: [notcurses_version_components()][c_api::notcurses_version_components].*
792    pub fn version_components() -> (u32, u32, u32, u32) {
793        let (mut major, mut minor, mut patch, mut tweak) = (0, 0, 0, 0);
794        unsafe {
795            c_api::notcurses_version_components(&mut major, &mut minor, &mut patch, &mut tweak);
796        }
797        (major as u32, minor as u32, patch as u32, tweak as u32)
798    }
799
800    /// Returns [`NcVisualGeometry`].
801    ///
802    /// If an [`NcVisual`] is not provided, only the [`cdim_yx`], [`blitter`],
803    /// [`scale_yx`], and [`maxpixel_yx`] fields will be filled in.
804    ///
805    /// If an [`NcVisualOptions`] is not provided, a default one will be used.
806    ///
807    /// Additionally `cdim_yx` and `maxpixel_yx` are only ever filled in if we
808    /// know them, and `maxpixel_yx` is only defined for [`NcBlitter::Pixel`].
809    ///
810    /// # See also
811    /// - [`NcVisual.geom`][NcVisual#method.geom]
812    ///
813    /// [`cdim_yx`]: NcVisualGeometry#structfield.cdim_yx
814    /// [`blitter`]: NcVisualGeometry#structfield.blitter
815    /// [`scale_yx`]: NcVisualGeometry#structfield.scale_yx
816    /// [`maxpixel_yx`]: NcVisualGeometry#structfield.maxpixel_yx
817    ///
818    /// *C style function: [ncvisual_geom()][c_api::ncvisual_geom].*
819    pub fn visual_geom(
820        &self,
821        visual: Option<&NcVisual>,
822        vopts: Option<&NcVisualOptions>,
823    ) -> NcResult<NcVisualGeometry> {
824        let mut vg = c_api::NcVGeom::new();
825
826        let v_ptr: *const NcVisual = if let Some(v) = visual { v } else { null() };
827        let vo_ptr: *const NcVisualOptions =
828            if let Some(o) = vopts { o } else { &NcVisualOptions::default() };
829
830        let res = unsafe { crate::c_api::ncvisual_geom(self, v_ptr, vo_ptr, &mut vg) };
831        if res <= c_api::NCRESULT_ERR {
832            return Err(NcError::with_msg(
833                res,
834                &format!["Nc.visual_geom({:?}, {:?})", visual, vopts],
835            ));
836        }
837
838        let (pix_yx, cdim_yx, rpix_yx, rcell_yx, scale_yx, maxpixel_yx, beg_yx, len_yx);
839
840        // if an `NcVisual` is not provided, only `maxpixel_yx`, `cdim_yx` and
841        // `scale_yx` can be non-zero.
842        if visual.is_none() {
843            cdim_yx = Some((vg.cdimy, vg.cdimx));
844            scale_yx = Some((vg.scaley, vg.scalex));
845
846            // pixel blitter only is defined for Ncblitter::PIXEL
847            if vg.blitter == NcBlitter::Pixel.into() {
848                maxpixel_yx = Some((vg.maxpixely, vg.maxpixelx));
849            } else {
850                maxpixel_yx = None;
851            }
852
853            pix_yx = None;
854            rpix_yx = None;
855            rcell_yx = None;
856            beg_yx = None;
857            len_yx = None;
858        } else {
859            // `maxpixel_yx` only is defined for `Ncblitter`::PIXEL.
860            if vg.blitter == NcBlitter::Pixel.into() {
861                maxpixel_yx = Some((vg.maxpixely, vg.maxpixelx));
862            } else {
863                maxpixel_yx = None;
864            }
865
866            // `beg_yx` & `len_yx` can be safely ignored if they're all 0.
867            if vg.begy | vg.begx | vg.leny | vg.lenx == 0 {
868                beg_yx = None;
869                len_yx = None;
870            } else {
871                beg_yx = Some((vg.begy, vg.begx));
872                len_yx = Some((vg.leny, vg.lenx));
873            }
874
875            // valid values for the following fields can't be 0 either:
876            if vg.pixy | vg.pixx == 0 {
877                pix_yx = None;
878            } else {
879                pix_yx = Some((vg.pixy, vg.pixx));
880            }
881            if vg.cdimy | vg.cdimx == 0 {
882                cdim_yx = None;
883            } else {
884                cdim_yx = Some((vg.cdimy, vg.cdimx));
885            }
886            if vg.scaley | vg.scalex == 0 {
887                scale_yx = None;
888            } else {
889                scale_yx = Some((vg.scaley, vg.scalex));
890            }
891            if vg.rpixy | vg.rpixx == 0 {
892                rpix_yx = None;
893            } else {
894                rpix_yx = Some((vg.rpixy, vg.rpixx));
895            }
896            if vg.rcelly | vg.rcellx == 0 {
897                rcell_yx = None;
898            } else {
899                rcell_yx = Some((vg.rcelly, vg.rcellx));
900            }
901        }
902
903        let vgeometry = NcVisualGeometry {
904            pix_yx,
905            cdim_yx,
906            rpix_yx,
907            rcell_yx,
908            scale_yx,
909            maxpixel_yx,
910            beg_yx,
911            len_yx,
912            blitter: (vg.blitter as crate::c_api::NcBlitter_u32).into(),
913        };
914        Ok(vgeometry)
915    }
916
917    /// Like [`visual_geom`] but auto-fills the `NcVisualOptions` with
918    /// `NcBlitter::Pixel` in order to get the maximum available resolution
919    /// for `scale_yx`, which determines the minimum dot-size for an `NcVisual`.
920    ///
921    /// [`visual_geom`]: Nc#method.visual_geom
922    pub fn visual_geom_with_pixel(&self, visual: Option<&NcVisual>) -> NcResult<NcVisualGeometry> {
923        Self::visual_geom(
924            self,
925            visual,
926            Some(&NcVisualOptions::builder().pixel().build()),
927        )
928    }
929}