Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion appcui/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "appcui"
version = "0.4.4"
version = "0.4.5"
edition = "2021"
authors = ["Gavrilut Dragos <gavrilut.dragos@gmail.com>"]
description = "A feature-rich and cross-platform TUI/CUI framework for Rust, enabling modern terminal-based applications on Windows, Linux, and macOS. Includes built-in UI components like buttons, menus, list views, tree views, checkboxes, and more. Perfect for building fast and interactive CLI tools and text-based interfaces."
Expand Down
1 change: 1 addition & 0 deletions appcui/src/backend/ncurses/implementation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,7 @@ impl Backend for NcursesTerminal {
}

fn on_close(&mut self) {
println!("\x1b[0m\x1b[2J\x1b[3J\x1b[H");
println!("\x1b[?1000l\x1b[?1002l\x1b[?1003l\x1b[?1006l")
}
}
2 changes: 0 additions & 2 deletions appcui/src/backend/termios/api/io.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
use std::{os::fd::AsRawFd, task::Poll};

use crate::{
backend::termios::api::TermiosError,
input::{Key, KeyModifier, MouseButton},
Expand Down
1 change: 1 addition & 0 deletions appcui/src/backend/termios/implementation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ impl Backend for TermiosTerminal {

fn on_close(&mut self) {
self.ansi_buffer.clear();
self.ansi_buffer.reset_screen();
self.ansi_buffer.disable_mouse_events();
self.ansi_buffer.execute();
self.orig_termios.restore();
Expand Down
6 changes: 6 additions & 0 deletions appcui/src/backend/utils/ansi_formatter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,12 @@ impl AnsiFormatter {
pub(crate) fn disable_mouse_events(&mut self) {
self.text.push_str("\x1b[?1000l\x1b[?1002l\x1b[?1003l\x1b[?1006l");
}

#[inline(always)]
pub(crate) fn reset_screen(&mut self) {
self.text.push_str("\x1b[0m\x1b[2J\x1b[3J\x1b[H");
}

#[inline(always)]
pub(crate) fn set_foreground_color(&mut self, color: Color) {
if self.flags.contains_one(AnsiFlags::Use16ColorSchema) {
Expand Down
2 changes: 2 additions & 0 deletions appcui/src/backend/utils/win32.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@ pub(crate) mod constants;
pub(crate) mod structs;
mod clipboard;
mod console;
mod original_screen;

#[cfg(test)]
mod tests;


pub(crate)use clipboard::Clipboard;
pub(crate)use console::Console;
use original_screen::OriginalScreen;



Expand Down
9 changes: 9 additions & 0 deletions appcui/src/backend/utils/win32/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,15 @@ extern "system" {
#[warn(non_camel_case_types)]
pub(crate) fn ReadConsoleInputW(handle: HANDLE, lpBuffer: *mut INPUT_RECORD, nLength: u32, lpNumberOfEventsRead: &mut u32) -> BOOL;

#[warn(non_camel_case_types)]
pub(crate) fn ReadConsoleOutputW(
handle: HANDLE,
lpBuffer: *mut CHAR_INFO,
dwBufferSize: COORD,
dwBufferCoord: COORD,
lpReadRegion: *mut SMALL_RECT,
) -> BOOL;

#[warn(non_camel_case_types)]
pub(crate) fn SetConsoleWindowInfo(handle: HANDLE, bAbsolute: BOOL, lpConsoleWindow: *const SMALL_RECT) -> BOOL;

Expand Down
16 changes: 13 additions & 3 deletions appcui/src/backend/utils/win32/console.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use std::sync::Arc;
use std::sync::Mutex;

use super::{api, constants, structs};
use crate::backend::utils::win32::OriginalScreen;
use crate::input::Key;
use crate::input::KeyCode;
use crate::input::KeyModifier;
Expand Down Expand Up @@ -31,6 +32,7 @@ pub(crate) struct Console {
shift_state: KeyModifier,
last_mouse_pos: Point,
shared_visible_region: Arc<Mutex<structs::SMALL_RECT>>,
orig_screen: Option<OriginalScreen>,
}

impl Console {
Expand Down Expand Up @@ -140,13 +142,17 @@ impl Console {
return Err(Error::new(
ErrorKind::InitializationFailure,
format!(
"SetConsoleScreenBufferInfoEx failed to sey a new color schema on current console !\nWindow code error: {}",
"SetConsoleScreenBufferInfoEx failed to set a new color schema on current console !\nWindow code error: {}",
api::GetLastError()
),
));
));
}
}

let orig_screen = if builder.restore_screen {
OriginalScreen::new(h_stdout, Size::new(w, h), info.window.left as i32, info.window.top as i32, info.cursor_pos)
} else {
None
};
Ok(Self {
stdin: h_stdin,
stdout: h_stdout,
Expand All @@ -157,6 +163,7 @@ impl Console {
shift_state: KeyModifier::None,
last_mouse_pos: Point::new(i32::MAX, i32::MAX),
shared_visible_region: Arc::new(Mutex::new(info.window)),
orig_screen,
})
}
}
Expand Down Expand Up @@ -354,6 +361,9 @@ impl Console {
let _ = api::SetConsoleMode(self.stdin, self.stdin_original_mode_flags);
let _ = api::SetConsoleMode(self.stdout, self.stdout_original_mode_flags);
}
if let Some(os) = self.orig_screen.take() {
os.restore();
}
}

fn screen_buffer_info(stdout: structs::HANDLE) -> Result<structs::CONSOLE_SCREEN_BUFFER_INFOEX, Error> {
Expand Down
72 changes: 72 additions & 0 deletions appcui/src/backend/utils/win32/original_screen.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
use super::api;
use super::constants;
use super::structs::*;
use crate::graphics::{Point, Size};

#[derive(Clone)]
pub(super) struct OriginalScreen {
stdout: HANDLE,
size: Size,
pos: Point,
data: Vec<CHAR_INFO>,
cursor_pos: COORD,
}

impl OriginalScreen {
pub(super) fn new(stdout: HANDLE, size: Size, x: i32, y: i32, cursor_pos: COORD) -> Option<Self> {
let sz = size.width as usize * size.height as usize;
let mut v: Vec<CHAR_INFO> = Vec::with_capacity(sz);
v.fill(CHAR_INFO { code: 0, attr: 0 });
let mut sr = SMALL_RECT {
left: x as i16,
top: y as i16,
right: ((x + size.width as i32) - 1) as i16,
bottom: ((y + size.height as i32) - 1) as i16,
};
unsafe {
if api::ReadConsoleOutputW(
stdout,
v.as_mut_ptr(),
COORD {
x: size.width as i16,
y: size.height as i16,
},
COORD { x: 0, y: 0 },
&mut sr,
) == constants::FALSE
{
return None;
}
}
Some(Self {
stdout,
size,
pos: Point::new(x, y),
data: v,
cursor_pos,
})
}
pub(super) fn restore(self) {
let sr = SMALL_RECT {
left: self.pos.x as i16,
top: self.pos.y as i16,
right: ((self.pos.x + self.size.width as i32) - 1) as i16,
bottom: ((self.pos.y + self.size.height as i32) - 1) as i16,
};
unsafe {
api::WriteConsoleOutputW(
self.stdout,
self.data.as_ptr(),
COORD {
x: self.size.width as i16,
y: self.size.height as i16,
},
COORD { x: 0, y: 0 },
&sr,
);
api::SetConsoleCursorPosition(self.stdout, self.cursor_pos);
let ci = CONSOLE_CURSOR_INFO { size: 10, visible: constants::TRUE };
api::SetConsoleCursorInfo(self.stdout, &ci);
}
}
}
4 changes: 4 additions & 0 deletions appcui/src/backend/windows_vt/implementation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ impl Backend for WindowsVTTerminal {
self.console.on_resize(new_size);
}
fn on_close(&mut self) {
self.ansi_formatter.clear();
self.ansi_formatter.reset_screen();
self.ansi_formatter.disable_mouse_events();
self.ansi_formatter.execute();
self.console.on_close();
}
fn update_screen(&mut self, surface: &Surface) {
Expand Down
17 changes: 15 additions & 2 deletions appcui/src/system/builder.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
use crate::backend;
use crate::graphics::*;
use crate::system::*;
use crate::ui::common::traits::*;
use crate::ui::common::*;
use crate::backend;


pub struct Builder {
pub(crate) size: Option<Size>,
pub(crate) backend: Option<backend::Type>,
Expand All @@ -18,6 +18,7 @@ pub struct Builder {
pub(crate) log_file: Option<String>,
pub(crate) log_append: bool,
pub(crate) use_color_schema: bool,
pub(crate) restore_screen: bool,
}
impl Builder {
pub(crate) fn new() -> Self {
Expand All @@ -35,6 +36,7 @@ impl Builder {
log_file: None,
log_append: false,
use_color_schema: true,
restore_screen: true,
}
}
/// Builds the application using the current settings.
Expand Down Expand Up @@ -106,4 +108,15 @@ impl Builder {
self.use_color_schema = enabled;
self
}

/// If enabled the backend will attempt to restore the original screen content and cursor position when the application ends.
/// If disabled, when the application ends the screen will be cleared.
/// By default this option is set.
///
/// **Remarks:** Not all backends have the support to restore the original screen (for those that do not have this support, the screen will always be cleared when application ends).
#[inline(always)]
pub fn restore_screen(mut self, enable: bool) -> Self {
self.restore_screen = enable;
self
}
}
2 changes: 2 additions & 0 deletions docs/chapter-2/application.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ Using `App::new` or `App::with_backend` creates a builder object that can furthe
* `.timers_count(count)` to set up the number of timers that can be used in the application (if not specified the default value is 4)
* `.log_file(path,append)` to set up a log file where logs will be displayed. This option will only be valid in **debug mode**. Once the file was specified, any call to [log!](logging.md) macro will be recorded in that file.
* `.color_schema(enabled)` if set this flag will try to use the terminal color schema, otherwise it will use AppCUI predefined values (e.g. for `Color::DarkBlue` will use `RGB(0,0,128)`). This flag is enabled by default.
* `.restore_screen(enabled)` if set the backend will attemp to restore the original screen status (content, cursor postion, etc) as it was before the application started. This option is **enabled** by default. Keep in mind that not all backends have this kind of support.

After setting up the configuration for an application, just call the `build()` method to create an application. This methods returns a result of type `Result<App,Error>` from where the appcui application can be obtained via several methods such as:
* `unwrap()` or `expect(...)` methods
Expand All @@ -47,6 +48,7 @@ let mut a = App::new().size(Size::new(80,40)) // size should be 80x25 char
.command_bar() // command bar should be enabled
.log_file("debug.log", false) // log into debug.log
.color_schema(false) // use AppCUI predefined colors
.restore_screen(true) // restore original screen when finished
.build()
.expect("Fail to create an AppCUI application");
```
Expand Down
13 changes: 8 additions & 5 deletions docs/chapter-2/backends.md
Original file line number Diff line number Diff line change
Expand Up @@ -160,10 +160,13 @@ System events are events that are not related to the keyboard or mouse, but are

## Other capabilities

| Capabilities | Windows Console | Windows VT | NCurses | Termios | Web Terminal | CrossTerm |
| ------------- | --------------- | ---------- | ------- | ------- | ------------ | --------- |
| Set dimension | Yes | Yes | - | - | Yes | - |
| Set title | Yes | Yes | - | - | Yes | - |
| Capabilities | Windows Console | Windows VT | NCurses | Termios | Web Terminal | CrossTerm |
| -------------- | --------------- | ---------- | ------- | ------- | ------------ | --------- |
| Set dimension | Yes | Yes | - | - | Yes | - |
| Set title | Yes | Yes | - | - | Yes | - |
| Restore screen | Yes | Yes | - | - | `N/A` | - |

* `N/A` (not applicable) means that there is no original screen prior to the app execution so there is nothing to restore.

## Clipboard

Expand All @@ -185,5 +188,5 @@ By default, when using initializing an `App` objct via `App::new()`, the folowin
| OS | Default backend | Other available backends |
| ------- | --------------- | ------------------------ |
| Windows | Windows Console | Windows VT, CrossTerm |
| Linux | NCurses | CrissTerm |
| Linux | NCurses | CrossTerm |
| Mac/OSX | Termios | NCurses, CrossTerm |
Loading