windows portable loading ui (#8490)

Signed-off-by: 21pages <sunboeasy@gmail.com>
This commit is contained in:
21pages 2024-06-27 11:04:55 +08:00 committed by GitHub
parent ef06b7d5d0
commit 12d3c59172
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 303 additions and 1 deletions

62
Cargo.lock generated
View File

@ -2139,7 +2139,7 @@ version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ad46a0e6c9bc688823a742aa969b5c08fdc56c2a436ee00d5c6fbcb5982c55c4"
dependencies = [
"libm",
"libm 0.2.8",
]
[[package]]
@ -3493,6 +3493,12 @@ dependencies = [
"windows-sys 0.48.0",
]
[[package]]
name = "libm"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fc7aa29613bd6a620df431842069224d8bc9011086b1db4c0e0cd47fa03ec9a"
[[package]]
name = "libm"
version = "0.2.8"
@ -3813,6 +3819,22 @@ dependencies = [
"tempfile",
]
[[package]]
name = "native-windows-gui"
version = "1.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4f7003a669f68deb6b7c57d74fff4f8e533c44a3f0b297492440ef4ff5a28454"
dependencies = [
"bitflags 1.3.2",
"lazy_static",
"newline-converter",
"plotters",
"plotters-backend",
"stretch",
"winapi 0.3.9",
"winapi-build",
]
[[package]]
name = "ndk"
version = "0.7.0"
@ -3891,6 +3913,15 @@ dependencies = [
"log",
]
[[package]]
name = "newline-converter"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f71d09d5c87634207f894c6b31b6a2b2c64ea3bdcf71bd5599fdbbe1600c00f"
dependencies = [
"unicode-segmentation",
]
[[package]]
name = "nix"
version = "0.23.2"
@ -4574,6 +4605,24 @@ dependencies = [
"time 0.3.30",
]
[[package]]
name = "plotters"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a15b6eccb8484002195a3e44fe65a4ce8e93a625797a063735536fd59cb01cf3"
dependencies = [
"num-traits 0.2.17",
"plotters-backend",
"wasm-bindgen",
"web-sys",
]
[[package]]
name = "plotters-backend"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "414cec62c6634ae900ea1c56128dfe87cf63e7caece0852ec76aba307cebadb7"
[[package]]
name = "png"
version = "0.17.10"
@ -5390,6 +5439,7 @@ dependencies = [
"brotli",
"dirs 5.0.1",
"md5",
"native-windows-gui",
"winapi 0.3.9",
"winres",
]
@ -5948,6 +5998,16 @@ version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe895eb47f22e2ddd4dabc02bce419d2e643c8e3b585c78158b349195bc24d82"
[[package]]
name = "stretch"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b0dc6d20ce137f302edf90f9cd3d278866fd7fb139efca6f246161222ad6d87"
dependencies = [
"lazy_static",
"libm 0.1.4",
]
[[package]]
name = "strsim"
version = "0.8.0"

View File

@ -14,6 +14,9 @@ dirs = "5.0"
md5 = "0.7"
winapi = { version = "0.3", features = ["winbase"] }
[target.'cfg(target_os = "windows")'.dependencies]
native-windows-gui = "1.0"
[package.metadata.winres]
LegalCopyright = "Copyright © 2024 Purslane Ltd. All rights reserved."
ProductName = "RustDesk"

View File

@ -8,6 +8,8 @@ use std::{
use bin_reader::BinaryReader;
pub mod bin_reader;
#[cfg(windows)]
mod ui;
#[cfg(windows)]
const APP_METADATA: &[u8] = include_bytes!("../app_metadata.toml");
@ -119,6 +121,11 @@ fn main() {
let click_setup = args.is_empty() && arg_exe.to_lowercase().ends_with("install.exe");
let quick_support = args.is_empty() && arg_exe.to_lowercase().ends_with("qs.exe");
#[cfg(windows)]
if args.is_empty() {
ui::setup();
}
let reader = BinaryReader::default();
if let Some(exe) = setup(
reader,

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

232
libs/portable/src/ui.rs Normal file
View File

@ -0,0 +1,232 @@
use native_windows_gui as nwg;
use nwg::NativeUi;
use std::cell::RefCell;
const GIF_DATA: &[u8] = include_bytes!("./res/spin.gif");
const LABEL_DATA: &[u8] = include_bytes!("./res/label.png");
const GIF_SIZE: i32 = 32;
const BG_COLOR: [u8; 3] = [90, 90, 120];
const BORDER_COLOR: [u8; 3] = [40, 40, 40];
const GIF_DELAY: u64 = 30;
#[derive(Default)]
pub struct BasicApp {
window: nwg::Window,
border_image: nwg::ImageFrame,
bg_image: nwg::ImageFrame,
gif_image: nwg::ImageFrame,
label_image: nwg::ImageFrame,
border_layout: nwg::GridLayout,
bg_layout: nwg::GridLayout,
inner_layout: nwg::GridLayout,
timer: nwg::AnimationTimer,
decoder: nwg::ImageDecoder,
gif_index: RefCell<usize>,
gif_images: RefCell<Vec<nwg::Bitmap>>,
}
impl BasicApp {
fn exit(&self) {
self.timer.stop();
nwg::stop_thread_dispatch();
}
fn load_gif(&self) -> Result<(), nwg::NwgError> {
let image_source = self.decoder.from_stream(GIF_DATA)?;
for frame_index in 0..image_source.frame_count() {
let image_data = image_source.frame(frame_index)?;
let image_data = self
.decoder
.resize_image(&image_data, [GIF_SIZE as u32, GIF_SIZE as u32])?;
let bmp = image_data.as_bitmap()?;
self.gif_images.borrow_mut().push(bmp);
}
Ok(())
}
fn update_gif(&self) -> Result<(), nwg::NwgError> {
let images = self.gif_images.borrow();
if images.len() == 0 {
return Err(nwg::NwgError::ImageDecoderError(
-1,
"no gif images".to_string(),
));
}
let image_index = *self.gif_index.borrow() % images.len();
self.gif_image.set_bitmap(Some(&images[image_index]));
*self.gif_index.borrow_mut() = (image_index + 1) % images.len();
Ok(())
}
fn start_timer(&self) {
self.timer.start();
}
}
mod basic_app_ui {
use super::*;
use native_windows_gui::{self as nwg, Bitmap};
use nwg::{Event, GridLayoutItem};
use std::cell::RefCell;
use std::ops::Deref;
use std::rc::Rc;
pub struct BasicAppUi {
inner: Rc<BasicApp>,
default_handler: RefCell<Vec<nwg::EventHandler>>,
}
impl nwg::NativeUi<BasicAppUi> for BasicApp {
fn build_ui(mut data: BasicApp) -> Result<BasicAppUi, nwg::NwgError> {
data.decoder = nwg::ImageDecoder::new()?;
let col_cnt: i32 = 7;
let row_cnt: i32 = 3;
let border_width: i32 = 1;
let window_size = (
GIF_SIZE * col_cnt + 2 * border_width,
GIF_SIZE * row_cnt + 2 * border_width,
);
// Controls
nwg::Window::builder()
.flags(nwg::WindowFlags::POPUP | nwg::WindowFlags::VISIBLE)
.size(window_size)
.center(true)
.build(&mut data.window)?;
nwg::ImageFrame::builder()
.parent(&data.window)
.size(window_size)
.background_color(Some(BORDER_COLOR))
.build(&mut data.border_image)?;
nwg::ImageFrame::builder()
.parent(&data.border_image)
.size((row_cnt * GIF_SIZE, col_cnt * GIF_SIZE))
.background_color(Some(BG_COLOR))
.build(&mut data.bg_image)?;
nwg::ImageFrame::builder()
.parent(&data.bg_image)
.size((GIF_SIZE, GIF_SIZE))
.background_color(Some(BG_COLOR))
.build(&mut data.gif_image)?;
nwg::ImageFrame::builder()
.parent(&data.bg_image)
.background_color(Some(BG_COLOR))
.bitmap(Some(&Bitmap::from_bin(LABEL_DATA)?))
.build(&mut data.label_image)?;
nwg::AnimationTimer::builder()
.parent(&data.window)
.interval(std::time::Duration::from_millis(GIF_DELAY))
.build(&mut data.timer)?;
// Wrap-up
let ui = BasicAppUi {
inner: Rc::new(data),
default_handler: Default::default(),
};
// Layouts
nwg::GridLayout::builder()
.parent(&ui.window)
.spacing(0)
.margin([0, 0, 0, 0])
.max_column(Some(1))
.max_row(Some(1))
.child_item(GridLayoutItem::new(&ui.border_image, 0, 0, 1, 1))
.build(&ui.border_layout)?;
nwg::GridLayout::builder()
.parent(&ui.border_image)
.spacing(0)
.margin([
border_width as _,
border_width as _,
border_width as _,
border_width as _,
])
.max_column(Some(1))
.max_row(Some(1))
.child_item(GridLayoutItem::new(&ui.bg_image, 0, 0, 1, 1))
.build(&ui.bg_layout)?;
nwg::GridLayout::builder()
.parent(&ui.bg_image)
.spacing(0)
.margin([0, 0, 0, 0])
.max_column(Some(col_cnt as _))
.max_row(Some(row_cnt as _))
.child_item(GridLayoutItem::new(&ui.gif_image, 2, 1, 1, 1))
.child_item(GridLayoutItem::new(&ui.label_image, 3, 1, 3, 1))
.build(&ui.inner_layout)?;
// Events
let evt_ui = Rc::downgrade(&ui.inner);
let handle_events = move |evt, _evt_data, _handle| {
if let Some(evt_ui) = evt_ui.upgrade().as_mut() {
match evt {
Event::OnWindowClose => {
evt_ui.exit();
}
Event::OnTimerTick => {
if let Err(e) = evt_ui.update_gif() {
eprintln!("{:?}", e);
}
}
_ => {}
}
}
};
ui.default_handler
.borrow_mut()
.push(nwg::full_bind_event_handler(
&ui.window.handle,
handle_events,
));
return Ok(ui);
}
}
impl Drop for BasicAppUi {
/// To make sure that everything is freed without issues, the default handler must be unbound.
fn drop(&mut self) {
let mut handlers = self.default_handler.borrow_mut();
for handler in handlers.drain(0..) {
nwg::unbind_event_handler(&handler);
}
}
}
impl Deref for BasicAppUi {
type Target = BasicApp;
fn deref(&self) -> &BasicApp {
&self.inner
}
}
}
fn ui() -> Result<(), nwg::NwgError> {
nwg::init()?;
let app = BasicApp::build_ui(Default::default())?;
app.load_gif()?;
app.start_timer();
nwg::dispatch_thread_events();
Ok(())
}
pub fn setup() {
std::thread::spawn(move || {
if let Err(e) = ui() {
eprintln!("{:?}", e);
}
});
}