├── 06_tabs ├── .gitignore ├── screen.gif ├── build.rs ├── resources │ └── resources.res ├── Cargo.toml ├── README.md └── src │ ├── main.rs │ ├── tab_container2.rs │ ├── my_window.rs │ └── tab_container1.rs ├── 01_button_click ├── .gitignore ├── screen.gif ├── build.rs ├── resources │ └── resources.res ├── Cargo.toml ├── README.md └── src │ ├── main.rs │ └── my_window.rs ├── 02_native_controls ├── .gitignore ├── screen.gif ├── build.rs ├── resources │ └── resources.res ├── Cargo.toml ├── README.md └── src │ ├── main.rs │ ├── wnd_decl.rs │ ├── wnd_new.rs │ └── wnd_events.rs ├── 03_dialog_resources ├── .gitignore ├── screen.gif ├── vs2019.gif ├── build.rs ├── resources │ └── resources.res ├── Cargo.toml ├── src │ ├── main.rs │ ├── ids.rs │ ├── my_window.rs │ └── my_modal.rs └── README.md ├── 04_custom_control ├── .gitignore ├── screen.gif ├── build.rs ├── resources │ └── resources.res ├── Cargo.toml ├── src │ ├── main.rs │ ├── my_window.rs │ └── click_board.rs └── README.md ├── 05_resizable_layout ├── .gitignore ├── screen.gif ├── build.rs ├── resources │ └── resources.res ├── Cargo.toml ├── README.md └── src │ ├── main.rs │ └── my_window.rs ├── 07_video_playback ├── .gitignore ├── screen.gif ├── build.rs ├── resources │ └── resources.res ├── rustfmt.toml ├── src │ ├── ids.rs │ ├── wnd_main │ │ ├── mod.rs │ │ ├── wnd_main_funcs.rs │ │ └── wnd_main_events.rs │ ├── main.rs │ ├── wnd_video │ │ ├── mod.rs │ │ ├── wnd_video_events.rs │ │ └── wnd_video_funcs.rs │ └── wnd_tracker │ │ ├── mod.rs │ │ ├── wnd_tracker_funcs.rs │ │ └── wnd_tracker_events.rs ├── Cargo.toml └── README.md ├── ch.sh ├── LICENSE.md └── README.md /06_tabs/.gitignore: -------------------------------------------------------------------------------- 1 | **/target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /01_button_click/.gitignore: -------------------------------------------------------------------------------- 1 | **/target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /02_native_controls/.gitignore: -------------------------------------------------------------------------------- 1 | **/target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /03_dialog_resources/.gitignore: -------------------------------------------------------------------------------- 1 | **/target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /04_custom_control/.gitignore: -------------------------------------------------------------------------------- 1 | **/target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /05_resizable_layout/.gitignore: -------------------------------------------------------------------------------- 1 | **/target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /07_video_playback/.gitignore: -------------------------------------------------------------------------------- 1 | **/target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /06_tabs/screen.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rodrigocfd/winsafe-examples/HEAD/06_tabs/screen.gif -------------------------------------------------------------------------------- /01_button_click/screen.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rodrigocfd/winsafe-examples/HEAD/01_button_click/screen.gif -------------------------------------------------------------------------------- /02_native_controls/screen.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rodrigocfd/winsafe-examples/HEAD/02_native_controls/screen.gif -------------------------------------------------------------------------------- /03_dialog_resources/screen.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rodrigocfd/winsafe-examples/HEAD/03_dialog_resources/screen.gif -------------------------------------------------------------------------------- /03_dialog_resources/vs2019.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rodrigocfd/winsafe-examples/HEAD/03_dialog_resources/vs2019.gif -------------------------------------------------------------------------------- /04_custom_control/screen.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rodrigocfd/winsafe-examples/HEAD/04_custom_control/screen.gif -------------------------------------------------------------------------------- /05_resizable_layout/screen.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rodrigocfd/winsafe-examples/HEAD/05_resizable_layout/screen.gif -------------------------------------------------------------------------------- /06_tabs/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | println!("cargo:rustc-link-lib=dylib:+verbatim=resources/resources.res"); 3 | } 4 | -------------------------------------------------------------------------------- /07_video_playback/screen.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rodrigocfd/winsafe-examples/HEAD/07_video_playback/screen.gif -------------------------------------------------------------------------------- /01_button_click/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | println!("cargo:rustc-link-lib=dylib:+verbatim=resources/resources.res"); 3 | } 4 | -------------------------------------------------------------------------------- /06_tabs/resources/resources.res: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rodrigocfd/winsafe-examples/HEAD/06_tabs/resources/resources.res -------------------------------------------------------------------------------- /02_native_controls/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | println!("cargo:rustc-link-lib=dylib:+verbatim=resources/resources.res"); 3 | } 4 | -------------------------------------------------------------------------------- /03_dialog_resources/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | println!("cargo:rustc-link-lib=dylib:+verbatim=resources/resources.res"); 3 | } 4 | -------------------------------------------------------------------------------- /04_custom_control/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | println!("cargo:rustc-link-lib=dylib:+verbatim=resources/resources.res"); 3 | } 4 | -------------------------------------------------------------------------------- /05_resizable_layout/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | println!("cargo:rustc-link-lib=dylib:+verbatim=resources/resources.res"); 3 | } 4 | -------------------------------------------------------------------------------- /07_video_playback/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | println!("cargo:rustc-link-lib=dylib:+verbatim=resources/resources.res"); 3 | } 4 | -------------------------------------------------------------------------------- /01_button_click/resources/resources.res: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rodrigocfd/winsafe-examples/HEAD/01_button_click/resources/resources.res -------------------------------------------------------------------------------- /02_native_controls/resources/resources.res: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rodrigocfd/winsafe-examples/HEAD/02_native_controls/resources/resources.res -------------------------------------------------------------------------------- /04_custom_control/resources/resources.res: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rodrigocfd/winsafe-examples/HEAD/04_custom_control/resources/resources.res -------------------------------------------------------------------------------- /07_video_playback/resources/resources.res: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rodrigocfd/winsafe-examples/HEAD/07_video_playback/resources/resources.res -------------------------------------------------------------------------------- /03_dialog_resources/resources/resources.res: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rodrigocfd/winsafe-examples/HEAD/03_dialog_resources/resources/resources.res -------------------------------------------------------------------------------- /05_resizable_layout/resources/resources.res: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rodrigocfd/winsafe-examples/HEAD/05_resizable_layout/resources/resources.res -------------------------------------------------------------------------------- /07_video_playback/rustfmt.toml: -------------------------------------------------------------------------------- 1 | chain_width = 60 2 | edition = "2024" 3 | hard_tabs = true 4 | match_block_trailing_comma = true 5 | struct_lit_width = 40 6 | use_field_init_shorthand = true 7 | use_small_heuristics = "Max" 8 | -------------------------------------------------------------------------------- /07_video_playback/src/ids.rs: -------------------------------------------------------------------------------- 1 | //! Control and menu IDs. 2 | 3 | use winsafe::seq_ids; 4 | 5 | seq_ids! { 6 | WND_VIDEO = 1000; 7 | WND_TRACKER 8 | } 9 | 10 | seq_ids! { 11 | MNU_FILE_OPEN = 2000; 12 | } 13 | 14 | pub const TIMER_ID: usize = 1; 15 | -------------------------------------------------------------------------------- /06_tabs/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tabs" 3 | version = "1.0.0" 4 | authors = ["Rodrigo César de Freitas Dias "] 5 | edition = "2024" 6 | 7 | [profile.release] 8 | lto = true 9 | strip = true 10 | codegen-units = 1 11 | 12 | [dependencies] 13 | winsafe = { version = "0.0.26", features = ["gui"] } 14 | -------------------------------------------------------------------------------- /01_button_click/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "button-click" 3 | version = "1.0.0" 4 | authors = ["Rodrigo César de Freitas Dias "] 5 | edition = "2024" 6 | 7 | [profile.release] 8 | lto = true 9 | strip = true 10 | codegen-units = 1 11 | 12 | [dependencies] 13 | winsafe = { version = "0.0.26", features = ["gui"] } 14 | -------------------------------------------------------------------------------- /02_native_controls/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "native-controls" 3 | version = "1.0.0" 4 | authors = ["Rodrigo César de Freitas Dias "] 5 | edition = "2024" 6 | 7 | [profile.release] 8 | lto = true 9 | strip = true 10 | codegen-units = 1 11 | 12 | [dependencies] 13 | winsafe = { version = "0.0.26", features = ["gui"] } 14 | -------------------------------------------------------------------------------- /02_native_controls/README.md: -------------------------------------------------------------------------------- 1 | # 02 Native controls 2 | 3 | This example showcases several native controls, and how to listen to their events. 4 | 5 | ![Example 02](screen.gif) 6 | 7 | To compile and run: 8 | 9 | ``` 10 | cargo run 11 | ``` 12 | 13 | To generate the final executable: 14 | 15 | ``` 16 | cargo build --release 17 | ``` 18 | -------------------------------------------------------------------------------- /03_dialog_resources/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "dialog-resources" 3 | version = "1.0.0" 4 | authors = ["Rodrigo César de Freitas Dias "] 5 | edition = "2024" 6 | 7 | [profile.release] 8 | lto = true 9 | strip = true 10 | codegen-units = 1 11 | 12 | [dependencies] 13 | winsafe = { version = "0.0.26", features = ["gui"] } 14 | -------------------------------------------------------------------------------- /04_custom_control/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "custom-control" 3 | version = "1.0.0" 4 | authors = ["Rodrigo César de Freitas Dias "] 5 | edition = "2024" 6 | 7 | [profile.release] 8 | lto = true 9 | strip = true 10 | codegen-units = 1 11 | 12 | [dependencies] 13 | winsafe = { version = "0.0.26", features = ["gui"] } 14 | -------------------------------------------------------------------------------- /05_resizable_layout/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "resizable-layout" 3 | version = "1.0.0" 4 | authors = ["Rodrigo César de Freitas Dias "] 5 | edition = "2024" 6 | 7 | [profile.release] 8 | lto = true 9 | strip = true 10 | codegen-units = 1 11 | 12 | [dependencies] 13 | winsafe = { version = "0.0.26", features = ["gui"] } 14 | -------------------------------------------------------------------------------- /07_video_playback/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "playback" 3 | version = "1.0.0" 4 | authors = ["Rodrigo César de Freitas Dias "] 5 | edition = "2024" 6 | 7 | [profile.release] 8 | lto = true 9 | strip = true 10 | codegen-units = 1 11 | 12 | [dependencies] 13 | winsafe = { version = "0.0.26", features = ["dshow", "gui", "mf", "shell"] } 14 | -------------------------------------------------------------------------------- /06_tabs/README.md: -------------------------------------------------------------------------------- 1 | # 06 Tabs 2 | 3 | This example shows how to use the `Tabs` widget, which keeps multiple container child windows, displaying only one at a time. 4 | 5 | ![Example 06](screen.gif) 6 | 7 | To compile and run: 8 | 9 | ``` 10 | cargo run 11 | ``` 12 | 13 | To generate the final executable: 14 | 15 | ``` 16 | cargo build --release 17 | ``` 18 | -------------------------------------------------------------------------------- /05_resizable_layout/README.md: -------------------------------------------------------------------------------- 1 | # 05 Resizable layout 2 | 3 | This example shows how to use the `gui::Horz` and `gui::Vert` enums to automatically resize the controls when a window is resized by the user. 4 | 5 | ![Example 05](screen.gif) 6 | 7 | To compile and run: 8 | 9 | ``` 10 | cargo run 11 | ``` 12 | 13 | To generate the final executable: 14 | 15 | ``` 16 | cargo build --release 17 | ``` 18 | -------------------------------------------------------------------------------- /05_resizable_layout/src/main.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] 2 | #![cfg_attr(any(), rustfmt::skip)] 3 | 4 | mod my_window; 5 | 6 | use winsafe::{self as w, prelude::*, co}; 7 | use my_window::MyWindow; 8 | 9 | fn main() { 10 | if let Err(e) = (|| MyWindow::new().run())() { 11 | w::HWND::NULL.MessageBox( 12 | &e.to_string(), "Uncaught error", co::MB::ICONERROR).unwrap(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /04_custom_control/src/main.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] 2 | #![cfg_attr(any(), rustfmt::skip)] 3 | 4 | mod click_board; 5 | mod my_window; 6 | 7 | use winsafe::{self as w, prelude::*, co}; 8 | use my_window::MyWindow; 9 | 10 | fn main() { 11 | if let Err(e) = (|| MyWindow::new().run())() { 12 | w::HWND::NULL.MessageBox( 13 | &e.to_string(), "Uncaught error", co::MB::ICONERROR).unwrap(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /03_dialog_resources/src/main.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] 2 | #![cfg_attr(any(), rustfmt::skip)] 3 | 4 | mod ids; 5 | mod my_modal; 6 | mod my_window; 7 | 8 | use winsafe::{self as w, prelude::*, co}; 9 | use my_window::MyWindow; 10 | 11 | fn main() { 12 | if let Err(e) = (|| MyWindow::new().run())() { 13 | w::HWND::NULL.MessageBox( 14 | &e.to_string(), "Uncaught error", co::MB::ICONERROR).unwrap(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /06_tabs/src/main.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] 2 | #![cfg_attr(any(), rustfmt::skip)] 3 | 4 | mod my_window; 5 | mod tab_container1; 6 | mod tab_container2; 7 | 8 | use winsafe::{self as w, prelude::*, co}; 9 | use my_window::MyWindow; 10 | 11 | fn main() { 12 | if let Err(e) = MyWindow::create_and_run() { 13 | w::HWND::NULL.MessageBox( 14 | &e.to_string(), "Uncaught error", co::MB::ICONERROR).unwrap(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /01_button_click/README.md: -------------------------------------------------------------------------------- 1 | # 01 Button click 2 | 3 | This is a very basic example showing a window with one button. 4 | 5 | The window is created programatically, and so is the button control. The button click event is handled with a closure, where the title of the window changes. 6 | 7 | ![Example 01](screen.gif) 8 | 9 | To compile and run: 10 | 11 | ``` 12 | cargo run 13 | ``` 14 | 15 | To generate the final executable: 16 | 17 | ``` 18 | cargo build --release 19 | ``` 20 | -------------------------------------------------------------------------------- /07_video_playback/src/wnd_main/mod.rs: -------------------------------------------------------------------------------- 1 | mod wnd_main_events; 2 | mod wnd_main_funcs; 3 | 4 | use std::cell::RefCell; 5 | use std::rc::Rc; 6 | use winsafe::{self as w, gui}; 7 | 8 | use crate::wnd_tracker::WndTracker; 9 | use crate::wnd_video::WndVideo; 10 | 11 | /// Main application window. 12 | #[derive(Clone)] 13 | pub struct WndMain { 14 | wnd: gui::WindowMain, 15 | wnd_video: WndVideo, 16 | wnd_tracker: WndTracker, 17 | taskbar: Rc>>, 18 | } 19 | -------------------------------------------------------------------------------- /02_native_controls/src/main.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] 2 | #![cfg_attr(any(), rustfmt::skip)] 3 | 4 | // Since MyWindow implementation is too long, we split it in three files. 5 | mod wnd_decl; 6 | mod wnd_events; 7 | mod wnd_new; 8 | 9 | use winsafe::{self as w, prelude::*, co}; 10 | use wnd_decl::MyWindow; 11 | 12 | fn main() { 13 | if let Err(e) = (|| MyWindow::new().run())() { 14 | w::HWND::NULL.MessageBox( 15 | &e.to_string(), "Uncaught error", co::MB::ICONERROR).unwrap(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /03_dialog_resources/src/ids.rs: -------------------------------------------------------------------------------- 1 | //! IDs of the resources defined in "resources\example03.res". 2 | //! 3 | //! They are the "glue" between our Rust code and the dialog resources. 4 | 5 | #![cfg_attr(any(), rustfmt::skip)] 6 | 7 | use winsafe::seq_ids; 8 | 9 | seq_ids! { 10 | ICO_MAIN = 101; 11 | } 12 | 13 | seq_ids! { 14 | DLG_MAIN = 1000; 15 | LBL_INPUT 16 | TXT_INPUT 17 | BTN_SHOW 18 | } 19 | 20 | seq_ids! { 21 | DLG_MODAL = 2000; 22 | LBL_INCOMING 23 | TXT_INCOMING 24 | LBL_RETURN 25 | TXT_RETURN 26 | BTN_OK 27 | BTN_CANCEL 28 | } 29 | -------------------------------------------------------------------------------- /07_video_playback/src/main.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] 2 | 3 | mod ids; 4 | mod wnd_main; 5 | mod wnd_tracker; 6 | mod wnd_video; 7 | 8 | use winsafe::{self as w, co, prelude::*}; 9 | 10 | fn main() { 11 | if let Err(e) = (|| { 12 | let _com_lib = 13 | w::CoInitializeEx(co::COINIT::APARTMENTTHREADED | co::COINIT::DISABLE_OLE1DDE)?; 14 | wnd_main::WndMain::create_and_run() 15 | })() { 16 | w::HWND::NULL 17 | .MessageBox(&e.to_string(), "Uncaught error", co::MB::ICONERROR) 18 | .unwrap(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /01_button_click/src/main.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] 2 | #![cfg_attr(any(), rustfmt::skip)] 3 | 4 | mod my_window; 5 | 6 | use winsafe::{prelude::*, co, AnyResult, HWND}; 7 | use my_window::MyWindow; 8 | 9 | fn main() { 10 | if let Err(e) = run_app() { 11 | HWND::NULL.MessageBox( 12 | &e.to_string(), "Uncaught error", co::MB::ICONERROR).unwrap(); 13 | } 14 | } 15 | 16 | fn run_app() -> AnyResult { 17 | MyWindow::new() // create our main window... 18 | .run() // ...and run it 19 | .map_err(|err| err.into()) 20 | } 21 | -------------------------------------------------------------------------------- /07_video_playback/src/wnd_video/mod.rs: -------------------------------------------------------------------------------- 1 | mod wnd_video_events; 2 | mod wnd_video_funcs; 3 | 4 | use std::cell::RefCell; 5 | use std::rc::Rc; 6 | use winsafe::{self as w, gui}; 7 | 8 | /// Child control which renders the video. 9 | #[derive(Clone)] 10 | pub struct WndVideo { 11 | wnd: gui::WindowControl, 12 | com_objs: Rc>>, 13 | } 14 | 15 | struct ComObjs { 16 | media_ctrl: w::IMediaControl, // drop order is important 17 | media_seek: w::IMediaSeeking, 18 | controller_evr: w::IMFVideoDisplayControl, 19 | vmr: w::IBaseFilter, 20 | graph_builder: w::IGraphBuilder, 21 | } 22 | -------------------------------------------------------------------------------- /04_custom_control/README.md: -------------------------------------------------------------------------------- 1 | # 04 Custom control 2 | 3 | This example builds a window with a custom control inside of it. The custom control cursor is not the traditional arrow, but a cross instead. Every time you click, a new line is drawn. 4 | 5 | Also, a counter in the title bar is incremented at each click. The custom control stores a callback sent by the main window, and this callback is fired at each click. 6 | 7 | ![Example 04](screen.gif) 8 | 9 | To compile and run: 10 | 11 | ``` 12 | cargo run 13 | ``` 14 | 15 | To generate the final executable: 16 | 17 | ``` 18 | cargo build --release 19 | ``` 20 | -------------------------------------------------------------------------------- /07_video_playback/src/wnd_tracker/mod.rs: -------------------------------------------------------------------------------- 1 | mod wnd_tracker_events; 2 | mod wnd_tracker_funcs; 3 | 4 | use std::cell::{Cell, RefCell}; 5 | use std::rc::Rc; 6 | use winsafe::{self as w, co, gui}; 7 | 8 | /// Child control which renders the progress bar at the bottom of the main 9 | /// window. 10 | #[derive(Clone)] 11 | pub struct WndTracker { 12 | wnd: gui::WindowControl, 13 | position_pct: Rc>, // 0 to 1 14 | click_cb: Rc w::AnyResult<()>>>>>, 15 | space_cb: Rc w::AnyResult<()>>>>>, 16 | arrows_cb: Rc w::AnyResult<()>>>>>, 17 | } 18 | -------------------------------------------------------------------------------- /02_native_controls/src/wnd_decl.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(any(), rustfmt::skip)] 2 | 3 | use winsafe::{self as w, gui}; 4 | 5 | /// Main application window. 6 | #[derive(Clone)] 7 | pub struct MyWindow { 8 | pub wnd: gui::WindowMain, 9 | pub btn: gui::Button, 10 | pub cmb: gui::ComboBox, 11 | pub chk: gui::CheckBox, 12 | pub dtp: gui::DateTimePicker, 13 | pub txt: gui::Edit, 14 | pub hea: gui::Header, 15 | pub lbl: gui::Label, 16 | pub lst: gui::ListBox, 17 | pub lsv: gui::ListView, 18 | pub mca: gui::MonthCalendar, 19 | pub pro: gui::ProgressBar, 20 | pub rad: gui::RadioGroup, 21 | pub sta: gui::StatusBar, 22 | pub tra: gui::Trackbar, 23 | pub tvw: gui::TreeView, 24 | } 25 | 26 | impl MyWindow { 27 | pub fn run(&self) -> w::AnyResult { 28 | self.wnd.run_main(None) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /ch.sh: -------------------------------------------------------------------------------- 1 | # Runs cargo check on each project. 2 | 3 | PROJS=( 4 | 01_button_click 5 | 02_native_controls 6 | 03_dialog_resources 7 | 04_custom_control 8 | 05_resizable_layout 9 | 06_tabs 10 | 07_video_playback 11 | ) 12 | 13 | set -e 14 | BLUE='\033[0;34m' 15 | PURP='\033[0;35m' 16 | NC='\033[0m' 17 | 18 | print_elapsed () { 19 | MIN=$(( ($1 - ($1 % (60 * 1000))) / (1000 * 60) )) 20 | SEC=$(( ($TF - ($MIN * 1000 * 60) - ($1 % 1000)) / 1000 )) 21 | MS=$(( $1 % 1000 )) 22 | 23 | if (($MIN > 0)); then 24 | printf " ${BLUE}Duration${NC} %02d:%02d.%03d min\n" $MIN $SEC $MS 25 | else 26 | printf " ${BLUE}Duration${NC} %d.%03d sec\n" $SEC $MS 27 | fi 28 | } 29 | 30 | for PROJ in "${PROJS[@]}" ; do 31 | printf "${PURP}${PROJ}${NC}...\n" 32 | T0=$(date +%s%N) 33 | 34 | cd $PROJ 35 | cargo check 36 | cd .. 37 | 38 | TF=$((($(date +%s%N) - $T0)/1000000)) 39 | print_elapsed $TF 40 | done 41 | -------------------------------------------------------------------------------- /03_dialog_resources/README.md: -------------------------------------------------------------------------------- 1 | # 03 Dialog resources 2 | 3 | In this example we create a main window and, when the button is clicked, a modal window. 4 | 5 | Instead of creating these windows programmatically, we load dialogs from the `resources/resources.res` resource file, which was created with the resource editor of Visual Studio 2019. You can edit this file with any [Win32 resource editor](https://en.wikipedia.org/wiki/Resource_(Windows)#Resource_software). 6 | 7 | Note how the modal receives a text and returns another. 8 | 9 | ![Example 03](screen.gif) 10 | 11 | To compile and run: 12 | 13 | ``` 14 | cargo run 15 | ``` 16 | 17 | To generate the final executable: 18 | 19 | ``` 20 | cargo build --release 21 | ``` 22 | 23 | ## Resource editor 24 | 25 | Any [Win32 resource editor](https://en.wikipedia.org/wiki/Resource_(Windows)#Resource_software) can be used to edit the resource file. 26 | 27 | Out of curiosity, this is the `resources/resource.res` file being edited in Visual Studio 2019 resource editor, using its WYSIWYG interface: 28 | 29 | ![Visual Studio 2019 resource editor](vs2019.gif) 30 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019-present, Rodrigo Cesar de Freitas Dias 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. -------------------------------------------------------------------------------- /06_tabs/src/tab_container2.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(any(), rustfmt::skip)] 2 | 3 | use winsafe::{self as w, gui, co, prelude::*}; 4 | 5 | /// Contents of second tab. 6 | #[derive(Clone)] 7 | pub struct TabContainer2 { 8 | wnd: gui::TabPage, 9 | cmb: gui::ComboBox, 10 | } 11 | 12 | impl Into for TabContainer2 { // so we can pass TabContainer2 to TabOpts 13 | fn into(self) -> gui::TabPage { 14 | self.wnd.clone() 15 | } 16 | } 17 | 18 | impl TabContainer2 { 19 | #[must_use] 20 | pub fn new(parent: &(impl GuiParent + 'static)) -> Self { 21 | let wnd = gui::TabPage::new( // create the window for tab page 1 22 | parent, 23 | gui::TabPageOpts::default(), 24 | ); 25 | 26 | let cmb = gui::ComboBox::new( // create a combobox 27 | &wnd, 28 | gui::ComboBoxOpts { 29 | position: gui::dpi(10, 10), 30 | items: &[ 31 | "Avocado", 32 | "Banana", 33 | "Grape", 34 | "Orange", 35 | ], 36 | selected_item: Some(0), 37 | ..Default::default() 38 | }, 39 | ); 40 | 41 | let new_self = Self { wnd, cmb }; 42 | new_self.events(); 43 | new_self 44 | } 45 | 46 | fn events(&self) { 47 | 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /07_video_playback/README.md: -------------------------------------------------------------------------------- 1 | # 07 Video playback 2 | 3 | This example is a video playback application which uses [DirectShow](https://docs.microsoft.com/en-us/windows/win32/directshow/directshow), a [COM](https://docs.microsoft.com/en-us/windows/win32/com/component-object-model--com--portal)-based API. 4 | 5 | The application is composed of 3 windows: 6 | 7 | * `WndMain` – the main application window, which hosts the other 2 windows; 8 | * `WndTracker` – the progress bar at the bottom; 9 | * `WndVideo` – hosts the DirectShow objects and renders the video. 10 | 11 | Note that the application includes no video codecs on its own: it will use video codecs already installed on your machine. If you try to load a video to which you don't have the proper codec, it will fail. 12 | 13 | Supported files: AVI, MKV and MP4. 14 | 15 | The following keyboard shortcuts are implemented: 16 | 17 | * Ctrl+O – open a video file; 18 | * Left or right – go backwards or forwards 10 seconds; 19 | * Space – play or pause; 20 | * Esc – close the application. 21 | 22 | ![Example 07](screen.gif) 23 | 24 | To compile and run: 25 | 26 | ``` 27 | cargo run 28 | ``` 29 | 30 | To generate the final executable: 31 | 32 | ``` 33 | cargo build --release 34 | ``` 35 | -------------------------------------------------------------------------------- /04_custom_control/src/my_window.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(any(), rustfmt::skip)] 2 | 3 | use winsafe::{self as w, prelude::*, gui, co}; 4 | 5 | use crate::click_board::ClickBoard; 6 | 7 | /// Main application window. 8 | #[derive(Clone)] 9 | pub struct MyWindow { 10 | wnd: gui::WindowMain, 11 | click_board: ClickBoard, 12 | } 13 | 14 | impl MyWindow { 15 | pub fn new() -> Self { 16 | let wnd = gui::WindowMain::new( 17 | gui::WindowMainOpts { 18 | title: "Custom control", 19 | class_icon: gui::Icon::Id(101), 20 | size: gui::dpi(300, 150), 21 | style: gui::WindowMainOpts::default().style | co::WS::MINIMIZEBOX, // add a minimize button 22 | ..Default::default() 23 | }, 24 | ); 25 | 26 | let click_board = ClickBoard::new( 27 | &wnd, 28 | gui::dpi(10, 10), 29 | gui::dpi(280, 130), 30 | ); 31 | 32 | let mut new_self = Self { wnd, click_board }; 33 | new_self.events(); 34 | new_self 35 | } 36 | 37 | pub fn run(&self) -> w::AnyResult { 38 | self.wnd.run_main(None) 39 | } 40 | 41 | fn events(&mut self) { 42 | let wnd = self.wnd.clone(); 43 | self.click_board.on_click(move |num_points| { // click event of our custom control 44 | wnd.hwnd().SetWindowText(&format!("Points: {}", num_points))?; 45 | Ok(()) 46 | }); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /06_tabs/src/my_window.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(any(), rustfmt::skip)] 2 | 3 | use winsafe::{self as w, gui, co, prelude::*}; 4 | 5 | use crate::tab_container1::TabContainer1; 6 | use crate::tab_container2::TabContainer2; 7 | 8 | /// Main application window. 9 | #[derive(Clone)] 10 | pub struct MyWindow { 11 | wnd: gui::WindowMain, 12 | tab_ctrl: gui::Tab, 13 | } 14 | 15 | impl MyWindow { 16 | pub fn create_and_run() -> w::AnyResult { 17 | let wnd = gui::WindowMain::new( // create the main window 18 | gui::WindowMainOpts { 19 | title: "Tabs", 20 | class_icon: gui::Icon::Id(101), 21 | size: gui::dpi(300, 150), 22 | style: gui::WindowMainOpts::default().style | co::WS::MINIMIZEBOX, // add a minimize button 23 | ..Default::default() 24 | }, 25 | ); 26 | 27 | let tab_ctrl = gui::Tab::new( // create the container tab control 28 | &wnd, 29 | gui::TabOpts { 30 | position: gui::dpi(10, 10), 31 | size: gui::dpi(280, 130), 32 | pages: &[ 33 | ("First", TabContainer1::new(&wnd).into()), // create the 2 tab pages 34 | ("Second", TabContainer2::new(&wnd).into()), 35 | ], 36 | ..Default::default() 37 | }, 38 | ); 39 | 40 | let new_self = Self { wnd, tab_ctrl }; 41 | new_self.events(); 42 | new_self.wnd.run_main(None) 43 | } 44 | 45 | fn events(&self) { 46 | 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /01_button_click/src/my_window.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(any(), rustfmt::skip)] 2 | 3 | use winsafe::{prelude::*, gui, AnyResult}; 4 | 5 | /// Main application window. 6 | #[derive(Clone)] 7 | pub struct MyWindow { 8 | wnd: gui::WindowMain, // responsible for managing the window 9 | btn_hello: gui::Button, // a button 10 | } 11 | 12 | impl MyWindow { 13 | pub fn new() -> Self { 14 | let wnd = gui::WindowMain::new( // instantiate the window manager 15 | gui::WindowMainOpts { 16 | title: "My window title", 17 | class_icon: gui::Icon::Id(101), // load icon from resource ID 101 18 | size: (300, 150), 19 | ..Default::default() // leave all other options as default 20 | }, 21 | ); 22 | 23 | let btn_hello = gui::Button::new( 24 | &wnd, // the window manager is the parent of our button 25 | gui::ButtonOpts { 26 | text: "&Click me", 27 | position: (20, 20), 28 | ..Default::default() 29 | }, 30 | ); 31 | 32 | let new_self = Self { wnd, btn_hello }; 33 | new_self.events(); // attach our events 34 | new_self 35 | } 36 | 37 | pub fn run(&self) -> AnyResult { 38 | self.wnd.run_main(None) // simply let the window manager do the hard work 39 | } 40 | 41 | fn events(&self) { 42 | let self2 = self.clone(); 43 | self.btn_hello.on().bn_clicked(move || { // button click event 44 | self2.wnd.hwnd().SetWindowText("Hello, world!")?; 45 | Ok(()) 46 | }); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /03_dialog_resources/src/my_window.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(any(), rustfmt::skip)] 2 | 3 | use winsafe::{self as w, prelude::*, gui}; 4 | 5 | use crate::ids; 6 | use crate::my_modal::MyModal; 7 | 8 | #[derive(Clone)] 9 | pub struct MyWindow { 10 | wnd: gui::WindowMain, 11 | lbl_input: gui::Label, 12 | txt_input: gui::Edit, 13 | btn_show: gui::Button, 14 | } 15 | 16 | impl MyWindow { 17 | pub fn new() -> Self { 18 | let dont_move = (gui::Horz::None, gui::Vert::None); 19 | 20 | let wnd = gui::WindowMain::new_dlg(ids::DLG_MAIN, Some(ids::ICO_MAIN), None); 21 | 22 | let lbl_input = gui::Label::new_dlg(&wnd, ids::LBL_INPUT, dont_move); 23 | let txt_input = gui::Edit::new_dlg(&wnd, ids::TXT_INPUT, dont_move); 24 | let btn_show = gui::Button::new_dlg(&wnd, ids::BTN_SHOW, dont_move); 25 | 26 | let new_self = Self { wnd, lbl_input, txt_input, btn_show }; 27 | new_self.events(); 28 | new_self 29 | } 30 | 31 | pub fn run(&self) -> w::AnyResult { 32 | self.wnd.run_main(None) 33 | } 34 | 35 | fn events(&self) { 36 | let self2 = self.clone(); 37 | self.btn_show.on().bn_clicked(move || { 38 | let input_text = self2.txt_input.text()?; 39 | 40 | let returned_text = MyModal::show(&self2.wnd, &input_text); // blocks until the modal is closed 41 | 42 | if let Some(text) = &returned_text { 43 | // If user clicked OK on the modal, a text is returned, 44 | // so we replace our current text with the new one. 45 | self2.txt_input.set_text(text)?; 46 | } 47 | Ok(()) 48 | }); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /06_tabs/src/tab_container1.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(any(), rustfmt::skip)] 2 | 3 | use winsafe::{self as w, gui, co, prelude::*}; 4 | 5 | /// Contents of first tab. 6 | #[derive(Clone)] 7 | pub struct TabContainer1 { 8 | wnd: gui::TabPage, 9 | txt: gui::Edit, 10 | btn: gui::Button, 11 | } 12 | 13 | impl Into for TabContainer1 { // so we can pass TabContainer1 to TabOpts 14 | fn into(self) -> gui::TabPage { 15 | self.wnd.clone() 16 | } 17 | } 18 | 19 | impl TabContainer1 { 20 | #[must_use] 21 | pub fn new(parent: &(impl GuiParent + 'static)) -> Self { 22 | let wnd = gui::TabPage::new( // create the window for tab page 1 23 | parent, 24 | gui::TabPageOpts::default(), 25 | ); 26 | 27 | let txt = gui::Edit::new( // create a textbox 28 | &wnd, 29 | gui::EditOpts { 30 | position: gui::dpi(20, 20), 31 | width: gui::dpi_x(180), 32 | text: "You", 33 | ..Default::default() 34 | }, 35 | ); 36 | 37 | let btn = gui::Button::new( // create a button 38 | &wnd, 39 | gui::ButtonOpts { 40 | position: gui::dpi(20, 52), 41 | text: "&Hello", 42 | ..Default::default() 43 | }, 44 | ); 45 | 46 | let new_self = Self { wnd, txt, btn }; 47 | new_self.events(); 48 | new_self 49 | } 50 | 51 | fn events(&self) { 52 | let self2 = self.clone(); 53 | self.btn.on().bn_clicked(move || { 54 | self2.wnd.hwnd().GetParent()?.TaskDialog( 55 | Some("Hello"), 56 | None, 57 | Some(&self2.txt.text()?), 58 | co::TDCBF::OK, 59 | w::IconRes::Info, 60 | )?; 61 | Ok(()) 62 | }); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /07_video_playback/src/wnd_tracker/wnd_tracker_funcs.rs: -------------------------------------------------------------------------------- 1 | use std::cell::{Cell, RefCell}; 2 | use std::rc::Rc; 3 | use winsafe::{self as w, co, gui, prelude::*}; 4 | 5 | use super::WndTracker; 6 | 7 | impl WndTracker { 8 | #[must_use] 9 | pub fn new( 10 | parent: &(impl GuiParent + 'static), 11 | ctrl_id: u16, 12 | position: (i32, i32), 13 | size: (i32, i32), 14 | ) -> Self { 15 | let wnd = gui::WindowControl::new( 16 | parent, 17 | gui::WindowControlOpts { 18 | ctrl_id, 19 | position, 20 | size, 21 | resize_behavior: (gui::Horz::Resize, gui::Vert::Repos), 22 | class_cursor: gui::Cursor::Idc(co::IDC::HAND), 23 | ..Default::default() 24 | }, 25 | ); 26 | 27 | let new_self = Self { 28 | wnd, 29 | position_pct: Rc::new(Cell::new(0.0)), 30 | click_cb: Rc::new(RefCell::new(None)), 31 | space_cb: Rc::new(RefCell::new(None)), 32 | arrows_cb: Rc::new(RefCell::new(None)), 33 | }; 34 | new_self.events(); 35 | new_self 36 | } 37 | 38 | pub fn set_rendered_pos(&self, position_pct: f32) -> w::SysResult<()> { 39 | self.position_pct.replace(position_pct); 40 | self.wnd.hwnd().InvalidateRect(None, true)?; 41 | Ok(()) 42 | } 43 | 44 | pub fn on_click(&self, cb: F) 45 | where 46 | F: Fn(f32) -> w::AnyResult<()> + 'static, 47 | { 48 | *self.click_cb.borrow_mut() = Some(Box::new(cb)); 49 | } 50 | 51 | pub fn on_space(&self, cb: F) 52 | where 53 | F: Fn() -> w::AnyResult<()> + 'static, 54 | { 55 | *self.space_cb.borrow_mut() = Some(Box::new(cb)); 56 | } 57 | 58 | pub fn on_arrows(&self, cb: F) 59 | where 60 | F: Fn(co::VK) -> w::AnyResult<()> + 'static, 61 | { 62 | *self.arrows_cb.borrow_mut() = Some(Box::new(cb)); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WinSafe examples 2 | 3 | This repo contains several examples of native Win32 applications written in [Rust](https://www.rust-lang.org) with [WinSafe](https://github.com/rodrigocfd/winsafe). All examples follow the same program structure, which is the recommended way to build a WinSafe application. 4 | 5 | Each directory is a full application, with is own `Cargo.toml`. 6 | 7 | ## Resources 8 | 9 | Each example has a `.res` file with its [application resources](https://en.wikipedia.org/wiki/Resource_(Windows)) (manifests, icons, dialogs and so on). You can edit the `.res` file with any resource editor, or even generate your own `.res` by compiling a `.rc` script. 10 | 11 | The `.res` file is linked into the final `.exe` by the `build.rs`, which is a [Cargo build script](https://doc.rust-lang.org/cargo/reference/build-scripts.html). 12 | 13 | ## Examples list 14 | 15 | Note that the order can change with new examples being added. 16 | 17 | | Example | Screenshot | 18 | | - | - | 19 | | 01 [Button click](01_button_click/) | ![Example 01](01_button_click/screen.gif) | 20 | | 02 [Native controls](02_native_controls/) | ![Example 02](02_native_controls/screen.gif) | 21 | | 03 [Dialog resources](03_dialog_resources/) | ![Example 03](03_dialog_resources/screen.gif) | 22 | | 04 [Custom control](04_custom_control/) | ![Example 04](04_custom_control/screen.gif) | 23 | | 05 [Resizable layout](05_resizable_layout/) | ![Example 05](05_resizable_layout/screen.gif) | 24 | | 06 [Tabs](06_tabs/) | ![Example 06](06_tabs/screen.gif) | 25 | | 07 [Video playback](07_video_playback/) | ![Example 07](07_video_playback/screen.gif) | 26 | 27 | ## License 28 | 29 | Licensed under [MIT license](https://opensource.org/licenses/MIT), see [LICENSE.md](LICENSE.md) for details. 30 | -------------------------------------------------------------------------------- /07_video_playback/src/wnd_video/wnd_video_events.rs: -------------------------------------------------------------------------------- 1 | use winsafe::{self as w, co, prelude::*}; 2 | 3 | use super::WndVideo; 4 | 5 | impl WndVideo { 6 | pub(super) fn events(&self) { 7 | let self2 = self.clone(); 8 | self.wnd.on().wm_close(move || { 9 | self2.unload()?; // cleanup 10 | self2.wnd.hwnd().EndDialog(0)?; 11 | Ok(()) 12 | }); 13 | 14 | let self2 = self.clone(); 15 | self.wnd.on().wm_paint(move || { 16 | let _hdc = self2.wnd.hwnd().BeginPaint()?; 17 | if let Some(com_objs) = self2.com_objs.try_borrow()?.as_ref() { 18 | com_objs.controller_evr.RepaintVideo()?; 19 | } 20 | Ok(()) 21 | }); 22 | 23 | // self.wnd.on().wm_erase_bkgnd(|_| 0); 24 | 25 | let self2 = self.clone(); 26 | self.wnd.on().wm_size(move |p| { 27 | if let Some(com_objs) = self2.com_objs.try_borrow()?.as_ref() { 28 | com_objs.controller_evr.SetVideoPosition( 29 | None, 30 | Some(w::RECT { 31 | left: 0, 32 | top: 0, 33 | right: p.client_area.cx, // resize the video to fit 34 | bottom: p.client_area.cy, 35 | }), 36 | )?; 37 | } 38 | Ok(()) 39 | }); 40 | 41 | let self2 = self.clone(); 42 | self.wnd.on().wm_l_button_down(move |_| { 43 | self2.wnd.hwnd().SetFocus(); 44 | self2.play_pause()?; // play/pause on left click 45 | Ok(()) 46 | }); 47 | 48 | let wnd = self.wnd.clone(); 49 | self.wnd.on().wm_r_button_down(move |_| { 50 | wnd.hwnd().SetFocus(); 51 | Ok(()) 52 | }); 53 | 54 | let self2 = self.clone(); 55 | self.wnd.on().wm_key_down(move |p| { 56 | if p.vkey_code == co::VK::SPACE { 57 | self2.play_pause()?; 58 | } 59 | Ok(()) 60 | }); 61 | 62 | let self2 = self.clone(); 63 | self.wnd.on().wm_get_dlg_code(move |p| { 64 | Ok(match p.vkey_code { 65 | co::VK::LEFT => { 66 | self2.seek_backwards(10 * 1000)?; 67 | co::DLGC::WANTARROWS 68 | }, 69 | co::VK::RIGHT => { 70 | self2.seek_forward(10 * 1000)?; 71 | co::DLGC::WANTARROWS 72 | }, 73 | _ => co::DLGC::NoValue, 74 | }) 75 | }); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /05_resizable_layout/src/my_window.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(any(), rustfmt::skip)] 2 | 3 | use winsafe::{self as w, gui, co}; 4 | 5 | /// Main application window. 6 | #[derive(Clone)] 7 | pub struct MyWindow { 8 | wnd: gui::WindowMain, 9 | lst: gui::ListView, 10 | txt: gui::Edit, 11 | btn: gui::Button, 12 | } 13 | 14 | impl MyWindow { 15 | pub fn new() -> Self { 16 | let wnd = gui::WindowMain::new( 17 | gui::WindowMainOpts { 18 | title: "Resizable layout", 19 | class_icon: gui::Icon::Id(101), 20 | size: gui::dpi(300, 150), 21 | style: gui::WindowMainOpts::default().style | 22 | co::WS::MINIMIZEBOX | co::WS::MAXIMIZEBOX | co::WS::SIZEBOX, // window can be resized 23 | ..Default::default() 24 | }, 25 | ); 26 | 27 | let lst = gui::ListView::new( 28 | &wnd, 29 | gui::ListViewOpts { 30 | position: gui::dpi(10, 10), 31 | size: gui::dpi(280, 100), 32 | // Resize horizontally and vertically together with parent window. 33 | resize_behavior: (gui::Horz::Resize, gui::Vert::Resize), 34 | ..Default::default() 35 | }, 36 | ); 37 | 38 | let txt = gui::Edit::new( 39 | &wnd, 40 | gui::EditOpts { 41 | position: gui::dpi(10, 120), 42 | width: gui::dpi_x(180), 43 | // Resize horizontally together with parent window. 44 | // Move anchored at bottom as parent window resizes. 45 | resize_behavior: (gui::Horz::Resize, gui::Vert::Repos), 46 | ..Default::default() 47 | }, 48 | ); 49 | 50 | let btn = gui::Button::new( 51 | &wnd, 52 | gui::ButtonOpts { 53 | text: "&Button", 54 | position: gui::dpi(200, 120), 55 | // Move anchored at right/bottom as parent window resizes. 56 | resize_behavior: (gui::Horz::Repos, gui::Vert::Repos), 57 | ..Default::default() 58 | }, 59 | ); 60 | 61 | let new_self = Self { wnd, lst, txt, btn }; 62 | new_self.events(); 63 | new_self 64 | } 65 | 66 | pub fn run(&self) -> w::AnyResult { 67 | self.wnd.run_main(None) 68 | } 69 | 70 | fn events(&self) { 71 | // no events being handled 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /07_video_playback/src/wnd_main/wnd_main_funcs.rs: -------------------------------------------------------------------------------- 1 | use std::cell::RefCell; 2 | use std::rc::Rc; 3 | use winsafe::{self as w, co, gui, prelude::*}; 4 | 5 | use super::WndMain; 6 | use crate::ids; 7 | use crate::wnd_tracker::WndTracker; 8 | use crate::wnd_video::WndVideo; 9 | 10 | impl WndMain { 11 | pub fn create_and_run() -> w::AnyResult { 12 | let (menu, accel_table) = Self::build_menu_acctbl().unwrap(); 13 | 14 | let wnd = gui::WindowMain::new(gui::WindowMainOpts { 15 | title: "DirectShow playback", 16 | style: gui::WindowMainOpts::default().style 17 | | co::WS::MINIMIZEBOX 18 | | co::WS::MAXIMIZEBOX 19 | | co::WS::SIZEBOX, 20 | class_icon: gui::Icon::Id(101), 21 | size: gui::dpi(700, 400), 22 | menu, 23 | accel_table: Some(accel_table), 24 | ..Default::default() 25 | }); 26 | 27 | let wnd_video = WndVideo::new(&wnd, ids::WND_VIDEO, gui::dpi(0, 0), gui::dpi(700, 380)); 28 | 29 | let wnd_tracker = 30 | WndTracker::new(&wnd, ids::WND_TRACKER, gui::dpi(0, 380), gui::dpi(700, 20)); 31 | 32 | let taskbar = Rc::new(RefCell::new(None)); // taskbar object initially not loaded 33 | 34 | let new_self = Self { wnd, wnd_video, wnd_tracker, taskbar }; 35 | new_self.events(); 36 | new_self.wnd.run_main(None) 37 | } 38 | 39 | #[must_use] 40 | fn build_menu_acctbl() -> w::AnyResult<(w::HMENU, w::guard::DestroyAcceleratorTableGuard)> { 41 | // Create file submenu. 42 | let file_submenu = w::HMENU::CreatePopupMenu()?; 43 | file_submenu.append_item(&[ 44 | w::MenuItem::Entry { 45 | cmd_id: ids::MNU_FILE_OPEN, 46 | text: "&Open video...\tCtrl+O", 47 | }, 48 | w::MenuItem::Separator, 49 | w::MenuItem::Entry { 50 | cmd_id: co::DLGID::CANCEL.into(), 51 | text: "E&xit", 52 | }, 53 | ])?; 54 | 55 | // Create main menu. 56 | let main_menu = w::HMENU::CreateMenu()?; 57 | main_menu.append_item(&[w::MenuItem::Submenu { submenu: &file_submenu, text: "&File" }])?; 58 | 59 | // Create accelerator table. 60 | let accel_table = w::HACCEL::CreateAcceleratorTable(&[w::ACCEL { 61 | fVirt: co::ACCELF::VIRTKEY | co::ACCELF::CONTROL, 62 | cmd: ids::MNU_FILE_OPEN, 63 | key: co::VK::CHAR_O, 64 | }])?; 65 | 66 | Ok((main_menu, accel_table)) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /07_video_playback/src/wnd_tracker/wnd_tracker_events.rs: -------------------------------------------------------------------------------- 1 | use winsafe::{self as w, co, prelude::*}; 2 | 3 | use super::WndTracker; 4 | 5 | impl WndTracker { 6 | pub(super) fn events(&self) { 7 | let self2 = self.clone(); 8 | self.wnd.on().wm_paint(move || { 9 | let has_focus = w::HWND::GetFocus().map_or(false, |h| h == *self2.wnd.hwnd()); 10 | 11 | let hdc = self2.wnd.hwnd().BeginPaint()?; 12 | 13 | let color = w::GetSysColor(if has_focus { 14 | co::COLOR::ACTIVECAPTION 15 | } else { 16 | co::COLOR::ACTIVEBORDER 17 | }); 18 | 19 | let hpen = w::HPEN::CreatePen(co::PS::SOLID, 1, color)?; 20 | let _hpen_old = hdc.SelectObject(&*hpen)?; 21 | 22 | let hbrush = w::HBRUSH::CreateSolidBrush(color)?; 23 | let _hbrush_old = hdc.SelectObject(&*hbrush)?; 24 | 25 | let rc = self2.wnd.hwnd().GetClientRect()?; 26 | let pos = rc.right as f32 * self2.position_pct.get(); 27 | hdc.Rectangle(w::RECT { 28 | left: 0, 29 | top: 0, 30 | right: pos as _, 31 | bottom: rc.bottom, 32 | })?; 33 | 34 | Ok(()) 35 | }); 36 | 37 | let self2 = self.clone(); 38 | self.wnd.on().wm_l_button_down(move |p| { 39 | self2.wnd.hwnd().SetFocus(); 40 | 41 | if let Some(click_cb) = self2.click_cb.try_borrow()?.as_ref() { 42 | let rc = self2.wnd.hwnd().GetClientRect()?; 43 | let pct = p.coords.x as f32 / rc.right as f32; 44 | click_cb(pct)?; 45 | } 46 | 47 | Ok(()) 48 | }); 49 | 50 | let wnd = self.wnd.clone(); 51 | self.wnd.on().wm_r_button_down(move |_| { 52 | wnd.hwnd().SetFocus(); 53 | Ok(()) 54 | }); 55 | 56 | let space_cb = self.space_cb.clone(); 57 | self.wnd.on().wm_key_down(move |p| { 58 | if p.vkey_code == co::VK::SPACE { 59 | if let Some(space_cb) = space_cb.try_borrow()?.as_ref() { 60 | space_cb()?; 61 | } 62 | } 63 | Ok(()) 64 | }); 65 | 66 | let self2 = self.clone(); 67 | self.wnd.on().wm_get_dlg_code(move |p| { 68 | if p.vkey_code == co::VK::LEFT || p.vkey_code == co::VK::RIGHT { 69 | if let Some(arrows_cb) = self2.arrows_cb.try_borrow()?.as_ref() { 70 | arrows_cb(p.vkey_code)?; 71 | } 72 | Ok(co::DLGC::WANTARROWS) 73 | } else { 74 | Ok(co::DLGC::NoValue) 75 | } 76 | }); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /04_custom_control/src/click_board.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(any(), rustfmt::skip)] 2 | 3 | use std::cell::RefCell; 4 | use std::rc::Rc; 5 | 6 | use winsafe::{self as w, prelude::*, co, gui}; 7 | 8 | /// A custom control which processes user clicks to draw lines. 9 | #[derive(Clone)] 10 | pub struct ClickBoard { 11 | wnd: gui::WindowControl, 12 | points: Rc>>, 13 | fn_click: Rc w::AnyResult<()>>>>>, // click callback 14 | } 15 | 16 | impl ClickBoard { 17 | pub fn new( 18 | parent: &(impl GuiParent + 'static), 19 | position: (i32, i32), 20 | size: (i32, i32), 21 | ) -> Self 22 | { 23 | let wnd = gui::WindowControl::new( 24 | parent, 25 | gui::WindowControlOpts { 26 | class_cursor: gui::Cursor::Idc(co::IDC::CROSS), 27 | ex_style: gui::WindowControlOpts::default().ex_style | co::WS_EX::CLIENTEDGE, 28 | position, 29 | size, 30 | ..Default::default() 31 | }, 32 | ); 33 | 34 | let new_self = Self { 35 | wnd, 36 | points: Rc::new(RefCell::new(Vec::default())), 37 | fn_click: Rc::new(RefCell::new(None)), 38 | }; 39 | 40 | new_self.events(); 41 | new_self 42 | } 43 | 44 | pub fn on_click(&mut self, func: F) 45 | where F: Fn(usize) -> w::AnyResult<()> + 'static, 46 | { 47 | *self.fn_click.borrow_mut() = Some(Box::new(func)); // store user callback 48 | } 49 | 50 | fn events(&self) { 51 | let self2 = self.clone(); 52 | self.wnd.on().wm_l_button_down(move |p| { 53 | let mut points = self2.points.borrow_mut(); 54 | points.push(p.coords); 55 | self2.wnd.hwnd().InvalidateRect(None, true)?; // redraw now 56 | 57 | if let Some(fn_click) = self2.fn_click.borrow().as_ref() { 58 | fn_click(points.len())?; // execute user callback 59 | } 60 | Ok(()) 61 | }); 62 | 63 | let self2 = self.clone(); 64 | self.wnd.on().wm_paint(move || { 65 | // Below, BeginPaint() returns a guard, which will call EndPaint() 66 | // automatically at the end of the current scope. 67 | let hdc = self2.wnd.hwnd().BeginPaint()?; 68 | 69 | // CreatePen() also returns a guard, which will call DeleteObject() 70 | // at the end of current scope. 71 | let pen = w::HPEN::CreatePen( 72 | co::PS::SOLID, 1, w::COLORREF::from_rgb(0, 0, 0xff))?; // blue color 73 | 74 | // SelectObject() also returns a guard, which will keep the replaced 75 | // GDI object and call SelectObject() again at the end of current 76 | // scope. We have no use for the returned guard, but we must keep it 77 | // alive, otherwise SelectObject() is called right away. 78 | let _old_pen = hdc.SelectObject(&*pen)?; 79 | 80 | hdc.MoveToEx(0, 0, None)?; // first drawn line starts from top left corner 81 | 82 | for pt in self2.points.borrow().iter() { 83 | hdc.LineTo(pt.x, pt.y)?; // draw all lines 84 | } 85 | 86 | // SelectObject()... 87 | // DeleteObject()... 88 | // EndPaint()... all called here, automatically by the guards 89 | 90 | Ok(()) 91 | }); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /03_dialog_resources/src/my_modal.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(any(), rustfmt::skip)] 2 | 3 | use std::cell::RefCell; 4 | use std::rc::Rc; 5 | use winsafe::{prelude::*, co, gui}; 6 | 7 | use crate::ids; 8 | 9 | #[derive(Clone)] 10 | pub struct MyModal { 11 | wnd: gui::WindowModal, 12 | 13 | lbl_incoming: gui::Label, 14 | txt_incoming: gui::Edit, 15 | lbl_return: gui::Label, 16 | txt_return: gui::Edit, 17 | btn_ok: gui::Button, 18 | btn_cancel: gui::Button, 19 | 20 | input_val: Rc>, // Rc/RefCell because MyModal will be cloned into closures 21 | return_val: Rc>>, 22 | } 23 | 24 | impl MyModal { 25 | /// Creates and displays the modal window. Blocks until the modal is closed 26 | /// by the user. 27 | pub fn show(parent: &impl GuiParent, input_text: &str) -> Option { 28 | let dont_move = (gui::Horz::None, gui::Vert::None); 29 | 30 | let wnd = gui::WindowModal::new_dlg(ids::DLG_MODAL); 31 | 32 | let lbl_incoming = gui::Label::new_dlg(&wnd, ids::LBL_INCOMING, dont_move); 33 | let txt_incoming = gui::Edit::new_dlg(&wnd, ids::TXT_INCOMING, dont_move); 34 | let lbl_return = gui::Label::new_dlg(&wnd, ids::LBL_RETURN, dont_move); 35 | let txt_return = gui::Edit::new_dlg(&wnd, ids::TXT_RETURN, dont_move); 36 | let btn_ok = gui::Button::new_dlg(&wnd, ids::BTN_OK, dont_move); 37 | let btn_cancel = gui::Button::new_dlg(&wnd, ids::BTN_CANCEL, dont_move); 38 | 39 | let new_self = Self { 40 | wnd, 41 | lbl_incoming, txt_incoming, 42 | lbl_return, txt_return, 43 | btn_ok, btn_cancel, 44 | input_val: Rc::new(RefCell::new(String::from(input_text))), 45 | return_val: Rc::new(RefCell::new(None)), 46 | }; 47 | 48 | new_self.events(); 49 | 50 | new_self.wnd.show_modal(parent).unwrap(); // blocks until the modal is closed 51 | new_self.return_val.borrow() 52 | .as_ref() 53 | .map(|s| s.clone()) // return the text typed in the modal, if any 54 | } 55 | 56 | fn events(&self) { 57 | // This event is fired right after the window is created, 58 | // and right before it appears on the screen. 59 | let self2 = self.clone(); 60 | self.wnd.on().wm_init_dialog(move |_| { 61 | self2.txt_incoming.set_text(&self2.input_val.try_borrow()?)?; 62 | Ok(true) 63 | }); 64 | 65 | let self2 = self.clone(); 66 | self.btn_ok.on().bn_clicked(move || { 67 | // Save the text typed by the user. 68 | *self2.return_val.try_borrow_mut()? = Some(self2.txt_return.text()?); 69 | self2.wnd.hwnd().EndDialog(0)?; 70 | Ok(()) 71 | }); 72 | 73 | let self2 = self.clone(); 74 | self.btn_cancel.on().bn_clicked(move || { 75 | *self2.return_val.try_borrow_mut()? = None; // no return text 76 | self2.wnd.hwnd().EndDialog(0)?; 77 | Ok(()) 78 | }); 79 | 80 | let self2 = self.clone(); 81 | self.wnd.on().wm_command_acc_menu(co::DLGID::CANCEL, move || { // ESC key 82 | *self2.return_val.try_borrow_mut()? = None; // no return text 83 | self2.wnd.hwnd().EndDialog(0)?; 84 | Ok(()) 85 | }); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /07_video_playback/src/wnd_main/wnd_main_events.rs: -------------------------------------------------------------------------------- 1 | use winsafe::{self as w, co, prelude::*}; 2 | 3 | use super::WndMain; 4 | use crate::ids; 5 | 6 | impl WndMain { 7 | pub(super) fn events(&self) { 8 | let self2 = self.clone(); 9 | self.wnd.on().wm_init_menu_popup(move |_| { 10 | self2.wnd_video.pause()?; // if a menu is shown, pause video 11 | Ok(()) 12 | }); 13 | 14 | let wnd = self.wnd.clone(); 15 | self.wnd 16 | .on() 17 | .wm_command_acc_menu(co::DLGID::CANCEL, move || { 18 | wnd.close(); // close on ESC 19 | Ok(()) 20 | }); 21 | 22 | let self2 = self.clone(); 23 | self.wnd 24 | .on() 25 | .wm_command_acc_menu(ids::MNU_FILE_OPEN, move || { 26 | let fileo = w::CoCreateInstance::( 27 | &co::CLSID::FileOpenDialog, 28 | None::<&w::IUnknown>, 29 | co::CLSCTX::INPROC_SERVER, 30 | )?; 31 | 32 | fileo.SetOptions( 33 | fileo.GetOptions()? 34 | | co::FOS::FORCEFILESYSTEM 35 | | co::FOS::ALLOWMULTISELECT 36 | | co::FOS::FILEMUSTEXIST, 37 | )?; 38 | 39 | fileo.SetFileTypes(&[ 40 | ("Video files", "*.avi;*.mkv;*.mp4"), 41 | ("AVI video files", "*.avi"), 42 | ("MKV video files", "*.mkv"), 43 | ("MP4 video files", "*.mp4"), 44 | ("All files", "*.*"), 45 | ])?; 46 | fileo.SetFileTypeIndex(1)?; 47 | 48 | if fileo.Show(self2.wnd.hwnd())? { 49 | self2 50 | .wnd_video 51 | .load(&fileo.GetResult()?.GetDisplayName(co::SIGDN::FILESYSPATH)?)?; 52 | 53 | let mut taskbar = self2.taskbar.try_borrow_mut()?; 54 | if taskbar.is_none() { 55 | // Taskbar object not created yet? 56 | *taskbar = Some(w::CoCreateInstance( 57 | &co::CLSID::TaskbarList, 58 | None::<&w::IUnknown>, 59 | co::CLSCTX::INPROC_SERVER, 60 | )?); 61 | } 62 | 63 | self2.wnd.hwnd().KillTimer(ids::TIMER_ID).ok(); // kill any previous timer 64 | self2.wnd.hwnd().SetTimer(ids::TIMER_ID, 100, None)?; // will fire WM_TIMER each 100 ms 65 | } 66 | Ok(()) 67 | }); 68 | 69 | let self2 = self.clone(); 70 | self.wnd_tracker.on_click(move |pct| { 71 | if let Some((_, ms_total)) = self2.wnd_video.curpos_duration()? { 72 | let ms_pos = pct * ms_total as f32; 73 | self2.wnd_video.set_pos(ms_pos as _)?; 74 | } 75 | Ok(()) 76 | }); 77 | 78 | let self2 = self.clone(); 79 | self.wnd_tracker.on_space(move || { 80 | self2.wnd_video.play_pause()?; 81 | 82 | if let Some(taskbar) = self2.taskbar.try_borrow()?.as_ref() { 83 | taskbar.SetProgressState( 84 | self2.wnd.hwnd(), 85 | if self2.wnd_video.is_running()? { 86 | co::TBPF::NORMAL // toggle taskbar green/yellow color 87 | } else { 88 | co::TBPF::PAUSED 89 | }, 90 | )?; 91 | } 92 | 93 | Ok(()) 94 | }); 95 | 96 | let wnd_video = self.wnd_video.clone(); 97 | self.wnd_tracker.on_arrows(move |key| { 98 | Ok(match key { 99 | co::VK::LEFT => wnd_video.seek_backwards(10 * 1000)?, 100 | co::VK::RIGHT => wnd_video.seek_forward(10 * 1000)?, 101 | _ => {}, 102 | }) 103 | }); 104 | 105 | let self2 = self.clone(); 106 | self.wnd.on().wm_timer(ids::TIMER_ID, move || { 107 | // Started when a video is loaded. 108 | if let Some((ms_cur, ms_total)) = self2.wnd_video.curpos_duration()? { 109 | self2.wnd.hwnd().SetWindowText(&format!( 110 | "{} / {}", 111 | ms_to_hms(ms_cur), 112 | ms_to_hms(ms_total) 113 | ))?; 114 | 115 | if let Some(taskbar) = self2.taskbar.try_borrow()?.as_ref() { 116 | taskbar.SetProgressValue(self2.wnd.hwnd(), ms_cur as _, ms_total as _)?; 117 | } 118 | 119 | self2 120 | .wnd_tracker 121 | .set_rendered_pos(ms_cur as f32 / ms_total as f32)?; 122 | } 123 | Ok(()) 124 | }); 125 | } 126 | } 127 | 128 | /// Converts milliseconds to formatted h:mm:ss. 129 | fn ms_to_hms(ms_cur: i64) -> String { 130 | let ms = ms_cur % 1000; 131 | let secs = ((ms_cur - ms) / 1000) % 60; 132 | let mins = ((ms_cur - secs * 1000 - ms) / 1000 / 60) % 60; 133 | let hs = (ms_cur - mins * 1000 * 60 - secs * 1000 - ms) / 1000 / 60 / 60; 134 | format!("{}:{:02}:{:02}", hs, mins, secs) 135 | } 136 | -------------------------------------------------------------------------------- /02_native_controls/src/wnd_new.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(any(), rustfmt::skip)] 2 | 3 | use winsafe::{co, gui}; 4 | 5 | use super::wnd_decl::MyWindow; 6 | 7 | impl MyWindow { 8 | pub fn new() -> Self { 9 | let wnd = gui::WindowMain::new( // first, we create the container window 10 | gui::WindowMainOpts { 11 | title: "The controls", 12 | class_icon: gui::Icon::Id(101), 13 | size: gui::dpi(660, 290), 14 | ..Default::default() 15 | }, 16 | ); 17 | 18 | // Now we create the child controls. 19 | 20 | // Note that we are creating each control manually, which means we must 21 | // specify the x,y coordinates by hand. This is very tedious. That's 22 | // why, when creating many controls, it's easier to use dialog boxes, 23 | // since we can place the controls visually by using a WYSIWYG resource 24 | // editor (like Visual Studio). 25 | 26 | let btn = gui::Button::new(&wnd, gui::ButtonOpts { 27 | text: "&Click me", 28 | position: gui::dpi(10, 10), 29 | resize_behavior: (gui::Horz::Repos, gui::Vert::Resize), 30 | ..Default::default() 31 | }); 32 | 33 | let cmb = gui::ComboBox::new(&wnd, gui::ComboBoxOpts { 34 | position: gui::dpi(110, 12), 35 | width: gui::dpi_x(120), 36 | items: &["banana", "avocado", "pineapple"], 37 | ..Default::default() 38 | }); 39 | 40 | let chk = gui::CheckBox::new(&wnd, gui::CheckBoxOpts { 41 | text: "C&heck me", 42 | position: gui::dpi(240, 15), 43 | ..Default::default() 44 | }); 45 | 46 | let dtp = gui::DateTimePicker::new(&wnd, gui::DateTimePickerOpts { 47 | position: gui::dpi(330, 12), 48 | ..Default::default() 49 | }); 50 | 51 | let txt = gui::Edit::new(&wnd, gui::EditOpts { 52 | position: gui::dpi(10, 50), 53 | width: gui::dpi_x(180), 54 | ..Default::default() 55 | }); 56 | 57 | let hea = gui::Header::new(&wnd, gui::HeaderOpts { 58 | position: gui::dpi(210, 50), 59 | width: gui::dpi_x(180), 60 | items: &[("First", 80), ("Second", 70)], 61 | ..Default::default() 62 | }); 63 | 64 | let lbl = gui::Label::new(&wnd, gui::LabelOpts { 65 | position: gui::dpi(410, 55), 66 | text: "&Label", 67 | ..Default::default() 68 | }); 69 | 70 | let lst = gui::ListBox::new(&wnd, gui::ListBoxOpts { 71 | position: gui::dpi(10, 90), 72 | items: &["First", "Second", "Third"], 73 | control_style: gui::ListBoxOpts::default().control_style | co::LBS::MULTIPLESEL, 74 | ..Default::default() 75 | }); 76 | 77 | let lsv = gui::ListView::new(&wnd, gui::ListViewOpts { 78 | position: gui::dpi(140, 90), 79 | columns: &[("First", 50), ("Second", 60)], 80 | control_style: co::LVS::REPORT | co::LVS::SHOWSELALWAYS, 81 | ..Default::default() 82 | }); 83 | 84 | let mca = gui::MonthCalendar::new(&wnd, gui::MonthCalendarOpts { 85 | position: gui::dpi(280, 90), 86 | ..Default::default() 87 | }); 88 | 89 | let pro = gui::ProgressBar::new(&wnd, gui::ProgressBarOpts { 90 | position: gui::dpi(470, 50), 91 | size: gui::dpi(160, 23), 92 | value: 40, 93 | ..Default::default() 94 | }); 95 | 96 | let rad = gui::RadioGroup::new(&wnd, &[ 97 | gui::RadioButtonOpts { 98 | text: "First radio", 99 | position: gui::dpi(15, 210), 100 | ..Default::default() 101 | }, 102 | gui::RadioButtonOpts { 103 | text: "Second radio", 104 | position: gui::dpi(15, 230), 105 | selected: true, 106 | ..Default::default() 107 | }, 108 | ]); 109 | 110 | let sta = gui::StatusBar::new(&wnd, &[ 111 | gui::SbPart::Fixed(200), 112 | gui::SbPart::Proportional(1), 113 | gui::SbPart::Proportional(1), 114 | ]); 115 | 116 | let tra = gui::Trackbar::new(&wnd, gui::TrackbarOpts { 117 | position: gui::dpi(140, 220), 118 | range: (0, 10), 119 | value: 4, 120 | ..Default::default() 121 | }); 122 | 123 | let tvw = gui::TreeView::new(&wnd, gui::TreeViewOpts { 124 | position: gui::dpi(520, 90), 125 | ..Default::default() 126 | }); 127 | 128 | // Create MyWindow object with the container window and all the controls. 129 | let new_self = Self { 130 | wnd, btn, cmb, chk, dtp, txt, hea, lbl, lst, lsv, mca, pro, rad, sta, tra, tvw, 131 | }; 132 | new_self.events(); // attach the events 133 | new_self // return MyWindow object 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /02_native_controls/src/wnd_events.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(any(), rustfmt::skip)] 2 | 3 | use winsafe::{self as w, prelude::*}; 4 | 5 | use super::wnd_decl::MyWindow; 6 | 7 | impl MyWindow { 8 | pub fn events(&self) { 9 | 10 | // There are a lot of events here, for educational purposes. 11 | // In a real-life application, you're unlikely to have that many events. 12 | 13 | let self2 = self.clone(); 14 | self.wnd.on().wm_create(move |_| { 15 | self2.lsv.items().add(&["Abc", "def"], None, ())?; 16 | self2.lsv.items().add(&["Ghi", "jkl"], None, ())?.select(true)?; 17 | self2.lsv.items().add(&["Zhi", "jkl"], None, ())?; 18 | 19 | self2.sta.parts().get(0).set_text("Status bar")?; 20 | 21 | let r1 = self2.tvw.items().add_root("Root 1", None, ())?; 22 | r1.add_child("Child 1", None, ())?; 23 | r1.expand(true)?; 24 | self2.tvw.items().add_root("Root 2", None, ())?; 25 | 26 | Ok(0) 27 | }); 28 | 29 | let self2 = self.clone(); 30 | self.btn.on().bn_clicked(move || { 31 | self2.wnd.hwnd().SetWindowText("Button clicked")?; 32 | self2.pro.set_position(95); 33 | Ok(()) 34 | }); 35 | 36 | let self2 = self.clone(); 37 | self.chk.on().bn_clicked(move || { 38 | let checked = self2.chk.is_checked(); 39 | self2.wnd.hwnd().SetWindowText(&format!("Status: {checked}"))?; 40 | Ok(()) 41 | }); 42 | 43 | let self2 = self.clone(); 44 | self.cmb.on().cbn_sel_change(move || { 45 | let sel = self2.cmb.items().selected_text()?.unwrap_or("(none)".to_owned()); 46 | self2.wnd.hwnd().SetWindowText(&format!("Selected: {sel}"))?; 47 | self2.sta.parts().get(1).set_text(&sel)?; 48 | Ok(()) 49 | }); 50 | 51 | let self2 = self.clone(); 52 | self.dtp.on().dtn_date_time_change(move |p| { 53 | let text = format!("{}-{}-{}", p.st.wYear, p.st.wMonth, p.st.wDay); 54 | self2.wnd.hwnd().SetWindowText(&format!("Date and time picker: {text}"))?; 55 | Ok(()) 56 | }); 57 | 58 | let self2 = self.clone(); 59 | self.txt.on().en_change(move || { 60 | let text = self2.txt.text()?; 61 | self2.wnd.hwnd().SetWindowText(&format!("Typed: {text}"))?; 62 | Ok(()) 63 | }); 64 | 65 | let self2 = self.clone(); 66 | self.hea.on().hdn_item_click(move |p| { 67 | let item = self2.hea.items().get(p.iItem as _); 68 | self2.wnd.hwnd().SetWindowText(&format!("Header: {}", item.text()))?; 69 | Ok(()) 70 | }); 71 | 72 | let self2 = self.clone(); 73 | self.lbl.on().stn_clicked(move || { 74 | self2.wnd.hwnd().SetWindowText("Label clicked")?; 75 | Ok(()) 76 | }); 77 | 78 | let self2 = self.clone(); 79 | self.lst.on().lbn_sel_change(move || { 80 | let text = if self2.lst.items().selected_count()? > 0 { 81 | self2.lst.items() 82 | .iter_selected()? 83 | .try_fold("".to_owned(), |mut str, idx_txt| { 84 | let (_idx, txt) = idx_txt?; 85 | str.push_str(&format!("{txt}, ")); 86 | w::SysResult::Ok(str) 87 | })? 88 | } else { 89 | "(no selection)".to_owned() 90 | }; 91 | self2.wnd.hwnd().SetWindowText(&text)?; 92 | Ok(()) 93 | }); 94 | 95 | let self2 = self.clone(); 96 | self.lsv.on().lvn_item_changed(move |_| { 97 | let text = if self2.lsv.items().selected_count() > 0 { 98 | self2.lsv.items() 99 | .iter_selected() 100 | .try_fold("".to_owned(), |mut str, item| { 101 | str.push_str(&format!("{}, ", item.text(0))); 102 | w::SysResult::Ok(str) 103 | })? 104 | } else { 105 | "(no selection)".to_owned() 106 | }; 107 | self2.wnd.hwnd().SetWindowText(&text)?; 108 | Ok(()) 109 | }); 110 | 111 | let self2 = self.clone(); 112 | self.lsv.header().unwrap().on().hdn_item_click(move |p| { 113 | let h = self2.lsv.header().unwrap().items().get(p.iItem as _); 114 | self2.wnd.hwnd().SetWindowText(&format!("Col: {}", h.text()))?; 115 | Ok(()) 116 | }); 117 | 118 | let self2 = self.clone(); 119 | self.mca.on().mcn_sel_change(move |p| { 120 | let text = format!("Month calendar: {}-{}-{}", 121 | p.stSelStart.wYear, p.stSelStart.wMonth, p.stSelStart.wDay); 122 | self2.wnd.hwnd().SetWindowText(&text)?; 123 | Ok(()) 124 | }); 125 | 126 | let self2 = self.clone(); 127 | self.rad.on().bn_clicked(move || { 128 | let text = self2.rad.selected().unwrap().hwnd().GetWindowText()?; 129 | self2.wnd.hwnd().SetWindowText(&text)?; 130 | Ok(()) 131 | }); 132 | 133 | let self2 = self.clone(); 134 | self.tra.on().wm_h_scroll(move |_| { 135 | self2.wnd.hwnd().SetWindowText(&format!("Pos: {}", self2.tra.pos()))?; 136 | Ok(()) 137 | }); 138 | 139 | let self2 = self.clone(); 140 | self.tvw.on().tvn_item_changed(move |_| { 141 | let text = match self2.tvw.items().iter_selected().next() { 142 | None => "(none)".to_owned(), 143 | Some(item) => item.text()?, 144 | }; 145 | self2.wnd.hwnd().SetWindowText(&format!("Tree: {text}"))?; 146 | Ok(()) 147 | }); 148 | 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /07_video_playback/src/wnd_video/wnd_video_funcs.rs: -------------------------------------------------------------------------------- 1 | use std::cell::RefCell; 2 | use std::rc::Rc; 3 | use winsafe::{self as w, co, gui, prelude::*}; 4 | 5 | use super::{ComObjs, WndVideo}; 6 | 7 | impl WndVideo { 8 | #[must_use] 9 | pub fn new( 10 | parent: &(impl GuiParent + 'static), 11 | ctrl_id: u16, 12 | position: (i32, i32), 13 | size: (i32, i32), 14 | ) -> Self { 15 | let wnd = gui::WindowControl::new( 16 | parent, 17 | gui::WindowControlOpts { 18 | ctrl_id, 19 | position, 20 | size, 21 | resize_behavior: (gui::Horz::Resize, gui::Vert::Resize), 22 | // ex_style: gui::WindowControlOpts::default().ex_style | co::WS_EX::CLIENTEDGE, 23 | ..Default::default() 24 | }, 25 | ); 26 | 27 | let com_objs = Rc::new(RefCell::new(None)); // COM objects initially not loaded 28 | 29 | let new_self = Self { wnd, com_objs }; 30 | new_self.events(); 31 | new_self 32 | } 33 | 34 | pub fn unload(&self) -> w::AnyResult<()> { 35 | if let Some(com_objs) = self.com_objs.try_borrow()?.as_ref() { 36 | com_objs.media_ctrl.Stop()?; 37 | } 38 | *self.com_objs.try_borrow_mut()? = None; // will drop all COM objects 39 | Ok(()) 40 | } 41 | 42 | pub fn load(&self, video_path: &str) -> w::AnyResult<()> { 43 | self.unload()?; 44 | 45 | let graph_builder = w::CoCreateInstance::( 46 | &co::CLSID::FilterGraph, 47 | None::<&w::IUnknown>, 48 | co::CLSCTX::INPROC_SERVER, 49 | )?; 50 | 51 | let vmr = w::CoCreateInstance::( 52 | &co::CLSID::EnhancedVideoRenderer, 53 | None::<&w::IUnknown>, 54 | co::CLSCTX::INPROC_SERVER, 55 | )?; 56 | 57 | graph_builder.AddFilter(&vmr, "EVR")?; 58 | 59 | let get_svc = vmr.QueryInterface::()?; 60 | 61 | let controller_evr = get_svc 62 | .GetService::(&co::MF_SERVICE::MR_VIDEO_RENDER_SERVICE)?; 63 | controller_evr.SetVideoWindow(self.wnd.hwnd())?; 64 | controller_evr.SetAspectRatioMode(co::MFVideoARMode::PreservePicture)?; 65 | 66 | graph_builder.RenderFile(video_path)?; 67 | 68 | let rc_s = self.wnd.hwnd().GetWindowRect()?; // first, in screen coordinates 69 | let rc_p = self.wnd.hwnd().ScreenToClientRc(rc_s)?; // now, relative to parent 70 | controller_evr.SetVideoPosition(None, Some(rc_p))?; // set video to fit window 71 | 72 | let media_seek = graph_builder.QueryInterface::()?; 73 | 74 | let media_ctrl = graph_builder.QueryInterface::()?; 75 | media_ctrl.Run()?; 76 | 77 | // let mut filter_info = w::FILTER_INFO::default(); 78 | // for filter in graph_builder.EnumFilters()?.iter() { 79 | // let filter = filter?; 80 | // filter.QueryFilterInfo(&mut filter_info)?; 81 | // println!("Filter: {}", filter_info.achName()); 82 | // } 83 | 84 | *self.com_objs.try_borrow_mut()? = Some( 85 | // finally save the COM objects 86 | ComObjs { 87 | graph_builder, 88 | vmr, 89 | controller_evr, 90 | media_seek, 91 | media_ctrl, 92 | }, 93 | ); 94 | Ok(()) 95 | } 96 | 97 | pub fn is_running(&self) -> w::AnyResult { 98 | Ok(match self.com_objs.try_borrow()?.as_ref() { 99 | Some(com_ojbs) => com_ojbs.media_ctrl.GetState(None)? == co::FILTER_STATE::Running, 100 | None => false, 101 | }) 102 | } 103 | 104 | pub fn play_pause(&self) -> w::AnyResult<()> { 105 | if let Some(com_objs) = self.com_objs.try_borrow()?.as_ref() { 106 | if self.is_running()? { 107 | com_objs.media_ctrl.Pause()?; 108 | } else { 109 | com_objs.media_ctrl.Run()?; 110 | } 111 | } 112 | Ok(()) 113 | } 114 | 115 | pub fn pause(&self) -> w::AnyResult<()> { 116 | if let Some(com_objs) = self.com_objs.try_borrow()?.as_ref() { 117 | com_objs.media_ctrl.Pause()?; 118 | } 119 | Ok(()) 120 | } 121 | 122 | pub fn curpos_duration(&self) -> w::AnyResult> { 123 | if let Some(com_objs) = self.com_objs.try_borrow()?.as_ref() { 124 | let cur_pos = com_objs.media_seek.GetCurrentPosition()? / 10_000; 125 | let duration = com_objs.media_seek.GetDuration()? / 10_000; 126 | Ok(Some((cur_pos, duration))) // originally in 100 nanoseconds; now in milliseconds 127 | } else { 128 | Ok(None) 129 | } 130 | } 131 | 132 | pub fn set_pos(&self, ms: i64) -> w::AnyResult<()> { 133 | if let Some(com_objs) = self.com_objs.try_borrow_mut()?.as_ref() { 134 | com_objs.media_seek.SetPositions( 135 | ms * 10_000, 136 | co::SEEKING_FLAGS::AbsolutePositioning, 137 | 0, 138 | co::SEEKING_FLAGS::NoPositioning, 139 | )?; 140 | } 141 | Ok(()) 142 | } 143 | 144 | pub fn seek_forward(&self, ms_diff: i64) -> w::AnyResult<()> { 145 | if let Some((ms_pos, ms_tot)) = self.curpos_duration()? { 146 | self.set_pos(if ms_pos + ms_diff >= ms_tot { 147 | ms_tot - 1 // never go beyond max 148 | } else { 149 | ms_pos + ms_diff 150 | })?; 151 | } 152 | Ok(()) 153 | } 154 | 155 | pub fn seek_backwards(&self, ms_diff: i64) -> w::AnyResult<()> { 156 | if let Some((ms_pos, _)) = self.curpos_duration()? { 157 | self.set_pos(if ms_diff > ms_pos { 158 | 0 // never go before zero 159 | } else { 160 | ms_pos - ms_diff 161 | })?; 162 | } 163 | Ok(()) 164 | } 165 | } 166 | --------------------------------------------------------------------------------