├── .gitignore ├── .travis.yml ├── Cargo.toml ├── LICENSE ├── README.md ├── SCREENSHOTS.md ├── build_docs.sh ├── examples ├── button_test.rs ├── progress_test.rs ├── show_image.rs ├── textbox_test.rs └── window_test.rs ├── kiss-app.manifest ├── screenshots ├── GTK+ │ ├── button_test.png │ ├── button_test_alert.png │ ├── button_test_message.png │ ├── progress_test.png │ ├── show_image.png │ ├── textbox_test.png │ └── window_test.png └── Windows │ ├── button_test.png │ ├── button_test_alert.png │ ├── button_test_message.png │ ├── progress_test.png │ ├── show_image.png │ ├── textbox_test.png │ └── window_test.png └── src ├── attrs.rs ├── base.rs ├── button.rs ├── callback.rs ├── container.rs ├── dialog.rs ├── image.rs ├── lib.rs ├── progress.rs ├── text.rs ├── timer.rs ├── utils ├── cstr.rs ├── mod.rs └── move_cell.rs └── widget.rs /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | 3 | Cargo.lock 4 | 5 | *~ 6 | *.swp -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | env: 2 | global: 3 | - IUP_DL='http://sourceforge.net/projects/iup/files/3.14/Linux%20Libraries/iup-3.14_Linux32_64_lib.tar.gz' 4 | 5 | language: rust 6 | 7 | install: 8 | - sudo apt-get install libgtk-3-dev 9 | 10 | # Download and install IUP 11 | - mkdir iup_libs/ 12 | - wget $IUP_DL -O iup_libs.tar.gz 13 | - tar -xzvf iup_libs.tar.gz -C iup_libs/ 14 | # By piping a newline to ./install, we skip the enter prompt 15 | - (cd iup_libs/ && echo -ne '\n' | sudo ./install) 16 | - rm -rf iup_libs/ 17 | 18 | script: 19 | - cargo build -v 20 | - cargo test -v 21 | - cargo doc -v --no-deps 22 | 23 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "kiss-ui" 3 | version = "0.1.0" 4 | authors = ["Austin Bonander "] 5 | 6 | [dependencies] 7 | libc = "*" 8 | iup-sys = "*" 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Austin Bonander 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | KISS-UI [![Build Status](https://travis-ci.org/cybergeek94/kiss-ui.svg?branch=master)](https://travis-ci.org/cybergeek94/kiss-ui) 2 | ========= 3 | A UI framework for Rust based on the KISS (Keep It Simple, Stupid!) philosophy. 4 | 5 | Powered by the [IUP][iup] GUI library for C by Tecgraf, via the bindings created for [iup-rust][iup-rust]. 6 | 7 | (No relation to the equally awesome [kiss3d][kiss3d].) 8 | 9 | [kiss3d]: https://github.com/sebcrozet/kiss3d 10 | [iup]: http://webserver2.tecgraf.puc-rio.br/iup/ 11 | [iup-rust]: https://github.com/dcampbell24/iup-rust 12 | 13 | Contents 14 | -------- 15 | 16 | * [Documentation](#documentation) 17 | * [Usage](#usage) 18 | * [Installing IUP Binaries](#installing-iup-binaries) 19 | * [Windows](#windows) 20 | * [Linux](#linux) 21 | * [OS X](#os-x) 22 | * [Comparison to Other UI Frameworks](#comparison-to-other-ui-frameworks) 23 | * [Enabling Visual Styles on Windows](#enabling-visual-styles-on-windows) 24 | 25 | Documentation 26 | ------------- 27 | [`kiss-ui` docs hosted on Github Pages](http://kiss-ui.github.io/kiss-ui/doc/kiss_ui/) 28 | 29 | Usage 30 | ----- 31 | 32 | Simply add the following to your `Cargo.toml`: 33 | 34 | ``` 35 | [dependencies.kiss-ui] 36 | git = "https://github.com/cybergeek94/kiss-ui" 37 | ``` 38 | 39 | Import KISS-UI's macros and common types: 40 | 41 | ```rust 42 | #[macro_use] 43 | extern crate kiss_ui; 44 | 45 | use kiss_ui::prelude::*; 46 | ``` 47 | 48 | #### KISS-UI builds on all Rust release channels! 49 | 50 | [iup-dl]: http://sourceforge.net/projects/iup/files/3.14/ 51 | 52 | Installing IUP Binaries 53 | ------------------- 54 | 55 | You will need to install the IUP binaries for your system, which are available for download [here][iup-dl]. 56 | 57 | Consult the following for which files to download and where to install them. The specific steps depend on your platform and preferred method of linking: dynamic or static. 58 | 59 | PRs amending or adding instructions for any platform are very welcome. 60 | 61 | *** 62 | ### Windows 63 | #### Dynamic linking 64 | 1. Navigate to `Windows Libraries/Dynamic` 65 | * 32-bit: Download `iup-3.14_Win32_dllw4_lib.zip` 66 | * 64-bit: Download `iup-3.14_Win64_dllw4_lib.zip` 67 | 2. Extract all `.dll` files to a folder where the linker can find them (pick one): 68 | * `/bin/rustlib//lib/` (recommended) 69 | * (using MinGW/MSYS) `/usr/lib` 70 | * `/bin/` 71 | 3. Copy the same DLLs to a folder in your PATH (pick one): 72 | * `/bin/` (recommended) 73 | * Create a folder anywhere and add it to your PATH. 74 | * Add one of the folders from step 2 to your PATH. 75 | 76 | You should **NEVER** place arbitrary files in your Windows install folder, no matter how benign. 77 | 78 | #### Static Linking 79 | Static linking with IUP on Windows is not currently possible as it requires resource scripts (`.rc`) files from IUP to be compiled and linked in, which Rust does not currently support. 80 | 81 | *** 82 | ### Linux 83 | The Linux binary packages for IUP include both static and dynamic libraries. While efforts are underway to create up-to-date packages for various distributions' package managers, the currently most well supported methods of obtaining IUP binaries are to either compile them from source or download precompiled binaries from the creators. 84 | 85 | 86 | #### Compile from Source 87 | To compile from source, see [this page][iup-compile]. The instructions to check-out the source tree are available [here][iup-source]. If you understand how to build projects with Makefiles, then it shouldn't be too difficult. 88 | 89 | #### Download the Precompiled Binaries 90 | However, if you would rather download the precompiled binaries, begin by going to [the download page][iup-dl]. 91 | 92 | 1. Navigate to the `Linux Libraries` folder. 93 | 2. Identify your kernel version. This can be done by entering the command `uname -r` into a terminal. 94 | * If you don't know if your Linux is 32-bit or 64-bit, use the command `uname -a` and look for the following: 95 | * `x86_64`: Your system is 64-bit. 96 | * `x86`: Your system is 32-bit. 97 | 3. Select and download the tarball for your kernel version and bit-arity. 98 | * For 32-bit (`x86`), there is only one package: `iup-3.14_Linux32_lib.tar.gz` 99 | * For 64-bit (`x86_64`), select one of the following based on your kernel version: 100 | * **>= 3.19**: `iup-3.14_Linux319_64_lib.tar.gz` 101 | * **>= 3.13**: `iup-3.14_Linux313_64_lib.tar.gz` 102 | * **>= 3.5**: `iup-3.14_Linux35_64_lib.tar.gz` 103 | * **>= 3.2**: `iup-3.14_Linux32_64_lib.tar.gz` 104 | * **2.6**: `iup-3.14_Linux26g4_64_lib.tar.gz` 105 | 4. Navigate to the folder where you downloaded the tarball to in a terminal. 106 | 5. Extract the tarball: 107 | * `mkdir iup_libs/` 108 | * `tar -xzvf -C iup_libs/` 109 | 6. Install the binaries: 110 | * `cd iup_libs/` (The install script must be run in its folder.) 111 | * You can run either, or both, of the following two commands: 112 | * To install the dynamic libraries: `sudo ./install` 113 | * To install the static libraries: `sudo ./install_dev` 114 | 7. Follow the prompts in the installer. 115 | 116 | Once the installer completes, you are finished. If you later want to uninstall IUP, open that `iup_libs/` folder in a terminal and run `sudo ./uninstall`. Otherwise, you may now delete the tarball and/or the `iup_libs/` folder. 117 | 118 | [iup-compile]: http://webserver2.tecgraf.puc-rio.br/iup/en/guide.html#buildlib 119 | [iup-source]: http://webserver2.tecgraf.puc-rio.br/iup/en/svn.html 120 | *** 121 | ### OS X 122 | 123 | Before you install IUP, you need to install GTK+. (An IUP driver for Cocoa was under development, but as of 7/5/2015 is not being worked on.) You can use version 2.x or 3.x, IUP will work with both. 124 | 125 | To install GTK+ 2: 126 | 127 | ``` 128 | brew install gtk+ 129 | ``` 130 | 131 | To install GTK+ 3: 132 | 133 | ``` 134 | brew install gtk+3 135 | ``` 136 | 137 | **Note:** if you have troubles building after installing GTK+ 3, please consult [this StackOverflow answer](http://stackoverflow.com/a/20114598/1299804). 138 | 139 | Once GTK+ is installed, you can download and install the precompiled Mac OS X IUP binary available [here][os-x]. It appears the only download available is for OS X 10.10 64-bit. 140 | 141 | Once you have downloaded the tarball, the installation process *should be* equivalent to Linux's starting at **Step 4**. 142 | 143 | [os-x]: http://sourceforge.net/projects/iup/files/3.14/Other%20Libraries/ 144 | 145 | *** 146 | 147 | Comparison to Other UI Frameworks 148 | --------------------------------- 149 | **NOTE**: This list is *far* from exhaustive and may contain outdated information. 150 | 151 | Pull requests for corrections and additions are welcome! 152 | 153 | * KISS-UI 154 | * Build Status: [![Build Status](https://travis-ci.org/cybergeek94/kiss-ui.svg?branch=master)](https://travis-ci.org/cybergeek94/kiss-ui) 155 | * Supported Platforms: Windows (using Win32 APIs), Linux and Mac (using GTK+) 156 | * Native Look and Feel: **Yes** 157 | * "Hello, World!" LOC: **[18][kiss-ui-hw]** 158 | * External Crates: **2** 159 | * External Native Libs: 1 160 | * [PistonDevelopers/conrod][conrod] 161 | * Build Status: [![Build Status](https://travis-ci.org/PistonDevelopers/conrod.svg?branch=master)](https://travis-ci.org/PistonDevelopers/conrod) 162 | * Supported Platforms: Windows, Mac, Linux 163 | * Native Look and Feel: No 164 | * "Hello, World!" LOC: [40][conrod-hw] (estimated based on linked example) 165 | * External Crates: 9 (not including testing crates and transitive dependencies) 166 | * External Native Libs: **~0** (depends on backend used) 167 | * [rust-gnome/gtk][rgtk] 168 | * Build Status: [![Build Status](https://travis-ci.org/rust-gnome/gtk.png?branch=master)](https://travis-ci.org/rust-gnome/gtk) 169 | * Supported Platforms: Windows, Mac, Linux 170 | * Native Look and Feel: **Yes** 171 | * "Hello, World!" LOC: [23][rust-gnome-hw] 172 | * External Crates: 10 (1 local but pulled from Crates.io) 173 | * External Native Libs: ~5 (installed on most Linux distros/external on Windows, Mac) 174 | 175 | Lines of code should be listed based on the `# sloc` stat on the Github file page. The raw linecount includes empty lines, which can arbitrarily affect the linecount. 176 | 177 | Enabling Visual Styles on Windows 178 | --------------------------------- 179 | Since Rust/Cargo currently do not support adding resource items to executables, Windows XP and later need an external manifest file to enable visual styles in KISS-UI applications. Otherwise the visual style will be Windows Classic. 180 | 181 | However, we have made this very simple to do! Simply copy the `kiss-app.manifest` file from this repo into the folder of your KISS-UI based executable, rename the file to `.manifest` (including the `.exe` extension, e.g. `my_executable.exe.manifest`), and run the executable as-is. You may need to delete and replace or rebuild the executable for this to take effect, as Windows appears to cache manifest file data, likely to avoid reparsing it on each run. 182 | 183 | Optionally, you can edit the `name=` and the `` values in the manifest file, using any text editor. However, it is unclear to the author what these actually affect. 184 | 185 | [kiss-ui-hw]: https://github.com/cybergeek94/kiss-ui/blob/master/examples/window_test.rs 186 | 187 | [conrod]: https://github.com/PistonDevelopers/conrod 188 | [conrod-hw]: https://github.com/PistonDevelopers/conrod/blob/master/examples/counter.rs 189 | 190 | [rust-gnome-hw]: https://github.com/rust-gnome/examples/blob/master/src/basic.rs 191 | 192 | [rgtk]: https://github.com/rust-gnome/gtk 193 | 194 | 195 | -------------------------------------------------------------------------------- /SCREENSHOTS.md: -------------------------------------------------------------------------------- 1 | KISS-UI Screenshots 2 | ------------------- 3 | 4 | Below are screenshots of the KISS-UI examples, on Windows 7 with Visual Styles enabled (see the README for more info), and on Linux Mint using the [Nightlife - Mint][nightlife-mint] theme. 5 | 6 | We at KISS-UI aren't exactly designers, so feel free to let us know how we can make these better aesthetically. 7 | 8 | [nightlife-mint]: http://cinnamon-spices.linuxmint.com/themes/view/31 9 | 10 | ###`examples/button_test.rs` 11 | 12 | This shows the main example as well as the dialogs that pop up when the "Message" and "Alert" buttons are clicked, respectively. 13 | 14 | ####Windows 15 | Not sure why the borders are cut off on the other dialogs. 16 | 17 | ![](screenshots/Windows/button_test.png) ![](screenshots/Windows/button_test_message.png) ![](screenshots/Windows/button_test_alert.png) 18 | 19 | ####GTK+ 20 | ![](screenshots/GTK+/button_test.png) ![](screenshots/GTK+/button_test_message.png) ![](screenshots/GTK+/button_test_alert.png) 21 | 22 | ###`examples/progress_test.rs` 23 | **Note**: the dashed version of the progress bar isn't always distinguished. It depends on the platform and visual theme being used. 24 | 25 | ####Windows 26 | ![](screenshots/Windows/progress_test.png) 27 | 28 | ####GTK+ 29 | ![](screenshots/GTK+/progress_test.png) 30 | 31 | ###`examples/show_image.rs` 32 | 33 | [Thanks to some wonderful suggestions on Reddit, this is no longer ugly!](http://www.reddit.com/r/rust/comments/37eezt/by_popular_demand_i_put_together_some_screenshots/crme9t8) 34 | 35 | ####Windows 36 | ![](screenshots/Windows/show_image.png) 37 | 38 | ####GTK+ 39 | ![](screenshots/GTK+/show_image.png) 40 | 41 | ###`examples/textbox_test.rs` 42 | This example shows the main dialog as well as the one that pops up when the "Save" button is clicked. 43 | 44 | ####Windows 45 | Notice the very faint drop-shadow around the right dialog, which is the currently focused one. This screencap was taken by selecting screen area instead of a specific window like the others. 46 | 47 | ![](screenshots/Windows/textbox_test.png) 48 | 49 | ####GTK+ 50 | The same was done in Linux. 51 | 52 | ![](screenshots/GTK+/textbox_test.png) 53 | 54 | ###`examples/window_test.rs` 55 | 56 | #####Note: resized to fit the page better. 57 | 58 | ####Windows 59 | ![](screenshots/Windows/window_test.png) 60 | 61 | 62 | ####GTK+ 63 | ![](screenshots/GTK+/window_test.png) 64 | 65 | -------------------------------------------------------------------------------- /build_docs.sh: -------------------------------------------------------------------------------- 1 | git merge master 2 | cargo doc 3 | rm -rf doc/ 4 | cp -avr target/doc/ doc/ 5 | -------------------------------------------------------------------------------- /examples/button_test.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate kiss_ui; 3 | 4 | use kiss_ui::prelude::*; 5 | 6 | use kiss_ui::button::Button; 7 | use kiss_ui::container::{Horizontal, Vertical}; 8 | use kiss_ui::dialog::{self, AlertPopupBuilder}; 9 | 10 | fn main() { 11 | kiss_ui::show_gui(|| 12 | Dialog::new( 13 | Vertical::new(children![ 14 | Horizontal::new( 15 | children![ 16 | Button::new() 17 | .set_label("Message #1") 18 | .set_name("Message_1") 19 | .set_onclick(show_message_dialog), 20 | Button::new() 21 | .set_label("Message #2") 22 | .set_name("Message_2") 23 | .set_onclick(show_message_dialog), 24 | Button::new() 25 | .set_label("change the name") 26 | .set_name("bar") 27 | .set_onclick(show_change_name), 28 | ] 29 | ) 30 | .set_elem_spacing_pixels(10), 31 | Horizontal::new( 32 | children![ 33 | Button::new() 34 | .set_label("Alert") 35 | .set_onclick(show_alert_dialog), 36 | Button::new() 37 | .set_label("Close") 38 | .set_onclick(close_dialog), 39 | ] 40 | ) 41 | .set_elem_spacing_pixels(10) 42 | ]) 43 | ) 44 | .set_title("Button test!") 45 | ) 46 | } 47 | 48 | fn show_message_dialog(btn: Button) { 49 | let name = btn.get_name().unwrap(); 50 | dialog::message_popup("Good job!", format!("You clicked the button {:?}!", name)); 51 | } 52 | 53 | fn show_change_name(btn: Button) { 54 | let name = btn.get_name().unwrap(); 55 | dialog::message_popup("Lets try it!", format!("The button's name is '{}'!\n 56 | Now we try to change it!\n 57 | You will get a Panic if you still use the 'name'", name)); 58 | // drop(name); // <= uncomment this line 59 | btn.set_name("foo"); 60 | show_message_dialog(btn); 61 | } 62 | 63 | fn show_alert_dialog(_: Button) { 64 | let res = AlertPopupBuilder::new("Alert!", "You clicked the other button!", "Yes") 65 | .button2("No") 66 | .button3("Cancel") 67 | .popup(); 68 | 69 | println!("Alert result = {}", res); 70 | } 71 | 72 | fn close_dialog(_: Button) -> CallbackStatus { 73 | println!("Closing dialog!"); 74 | CallbackStatus::Close 75 | } 76 | -------------------------------------------------------------------------------- /examples/progress_test.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate kiss_ui; 3 | 4 | use kiss_ui::callback::OnShow; 5 | use kiss_ui::container::Vertical; 6 | use kiss_ui::dialog::Dialog; 7 | use kiss_ui::progress::ProgressBar; 8 | use kiss_ui::text::Label; 9 | use kiss_ui::timer::Timer; 10 | 11 | fn main() { 12 | kiss_ui::show_gui(|| { 13 | let regular = ProgressBar::new(); 14 | let dashed = ProgressBar::new().set_dashed(true); 15 | 16 | let dialog = Dialog::new( 17 | Vertical::new( 18 | children![ 19 | Label::new("Regular:"), 20 | regular.clone(), 21 | Label::new("Dashed:"), 22 | dashed.clone(), 23 | Label::new("Indefinite:"), 24 | ProgressBar::new().set_indefinite(true), 25 | ] 26 | ) 27 | ); 28 | 29 | dialog 30 | .set_title("Progressbar Test") 31 | .set_on_show(move |_| { 32 | let on_timer_interval = move |timer: Timer|{ 33 | regular.add_value(0.1); 34 | dashed.add_value(0.1); 35 | 36 | if regular.get_value() == 1.0 { 37 | timer.stop(); 38 | } 39 | }; 40 | 41 | Timer::new() 42 | .set_interval(1000) 43 | .set_on_interval(on_timer_interval) 44 | .start(); 45 | }); 46 | 47 | dialog 48 | }); 49 | } 50 | 51 | -------------------------------------------------------------------------------- /examples/show_image.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate kiss_ui; 3 | 4 | use kiss_ui::container::Horizontal; 5 | use kiss_ui::dialog::Dialog; 6 | use kiss_ui::image::{Image, ImageContainer}; 7 | use kiss_ui::text::Label; 8 | 9 | fn main() { 10 | const WIDTH: u32 = 512; 11 | const HEIGHT: u32 = 512; 12 | 13 | let image_data: Vec<_> = (0..HEIGHT) 14 | .flat_map(|y| (0..WIDTH).map(move |x| color(x, y))) 15 | .collect(); 16 | 17 | kiss_ui::show_gui(|| { 18 | Dialog::new( 19 | Horizontal::new( 20 | children![ 21 | Label::new("") 22 | .set_image(Image::new_rgb(WIDTH, HEIGHT, &image_data)), 23 | ] 24 | ) 25 | ) 26 | .set_title(format!("Image! ({width} x {height})", width=WIDTH, height=HEIGHT)) 27 | }); 28 | } 29 | 30 | /// Play with this function and let us know what you come up with! 31 | fn color(x: u32, y: u32) -> (u8, u8, u8) { 32 | // Suggested by /u/GBGamer117 33 | ((x ^ y) as u8, y as u8, x as u8) 34 | 35 | // Suggested by /u/Effnote 36 | // ((x ^ y) as u8, ((x + 2) ^ (y + 1)) as u8, ((x + 4) ^ (y + 2)) as u8) 37 | 38 | // Suggested by /u/ImSoCabbage 39 | // let val = (x ^ y) as u8; 40 | // (val, val, val) 41 | // (255 - val, val, val) // Inverted red 42 | } 43 | 44 | -------------------------------------------------------------------------------- /examples/textbox_test.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate kiss_ui; 3 | 4 | use kiss_ui::prelude::*; 5 | 6 | use kiss_ui::button::Button; 7 | use kiss_ui::container::Vertical; 8 | use kiss_ui::dialog; 9 | use kiss_ui::text::{Label, TextBox}; 10 | 11 | fn main() { 12 | kiss_ui::show_gui(|| { 13 | Dialog::new( 14 | Vertical::new( 15 | children![ 16 | Label::new("Enter a message:"), 17 | TextBox::new() 18 | .set_visible_columns(20) 19 | .set_name("my_textbox"), 20 | Button::new() 21 | .set_label("Save") 22 | .set_onclick(show_alert_message), 23 | ] 24 | ) 25 | ) 26 | .set_title("Textbox Test") 27 | }); 28 | } 29 | 30 | fn show_alert_message(clicked: Button) { 31 | let dialog = clicked.get_dialog().unwrap(); 32 | let text_box = dialog.get_child("my_textbox").unwrap() 33 | .try_downcast::().ok().expect("child my_textbox was not a TextBox!"); 34 | let text = text_box.get_text(); 35 | 36 | dialog::message_popup("Message saved!", format!("Your message: {}", text)); 37 | } 38 | -------------------------------------------------------------------------------- /examples/window_test.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate kiss_ui; 3 | 4 | use kiss_ui::container::Horizontal; 5 | use kiss_ui::dialog::Dialog; 6 | use kiss_ui::text::Label; 7 | 8 | fn main() { 9 | kiss_ui::show_gui(|| { 10 | Dialog::new( 11 | Horizontal::new( 12 | children![ 13 | Label::new("Hello, world!"), 14 | ] 15 | ) 16 | ) 17 | .set_title("Hello, world!") 18 | .set_size_pixels(640, 480) 19 | }); 20 | } 21 | -------------------------------------------------------------------------------- /kiss-app.manifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | KISS-UI Application 10 | 11 | 12 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /screenshots/GTK+/button_test.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KISS-UI/kiss-ui/074bf82df1b90739bd0c6df094c21aba633d1c49/screenshots/GTK+/button_test.png -------------------------------------------------------------------------------- /screenshots/GTK+/button_test_alert.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KISS-UI/kiss-ui/074bf82df1b90739bd0c6df094c21aba633d1c49/screenshots/GTK+/button_test_alert.png -------------------------------------------------------------------------------- /screenshots/GTK+/button_test_message.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KISS-UI/kiss-ui/074bf82df1b90739bd0c6df094c21aba633d1c49/screenshots/GTK+/button_test_message.png -------------------------------------------------------------------------------- /screenshots/GTK+/progress_test.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KISS-UI/kiss-ui/074bf82df1b90739bd0c6df094c21aba633d1c49/screenshots/GTK+/progress_test.png -------------------------------------------------------------------------------- /screenshots/GTK+/show_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KISS-UI/kiss-ui/074bf82df1b90739bd0c6df094c21aba633d1c49/screenshots/GTK+/show_image.png -------------------------------------------------------------------------------- /screenshots/GTK+/textbox_test.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KISS-UI/kiss-ui/074bf82df1b90739bd0c6df094c21aba633d1c49/screenshots/GTK+/textbox_test.png -------------------------------------------------------------------------------- /screenshots/GTK+/window_test.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KISS-UI/kiss-ui/074bf82df1b90739bd0c6df094c21aba633d1c49/screenshots/GTK+/window_test.png -------------------------------------------------------------------------------- /screenshots/Windows/button_test.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KISS-UI/kiss-ui/074bf82df1b90739bd0c6df094c21aba633d1c49/screenshots/Windows/button_test.png -------------------------------------------------------------------------------- /screenshots/Windows/button_test_alert.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KISS-UI/kiss-ui/074bf82df1b90739bd0c6df094c21aba633d1c49/screenshots/Windows/button_test_alert.png -------------------------------------------------------------------------------- /screenshots/Windows/button_test_message.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KISS-UI/kiss-ui/074bf82df1b90739bd0c6df094c21aba633d1c49/screenshots/Windows/button_test_message.png -------------------------------------------------------------------------------- /screenshots/Windows/progress_test.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KISS-UI/kiss-ui/074bf82df1b90739bd0c6df094c21aba633d1c49/screenshots/Windows/progress_test.png -------------------------------------------------------------------------------- /screenshots/Windows/show_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KISS-UI/kiss-ui/074bf82df1b90739bd0c6df094c21aba633d1c49/screenshots/Windows/show_image.png -------------------------------------------------------------------------------- /screenshots/Windows/textbox_test.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KISS-UI/kiss-ui/074bf82df1b90739bd0c6df094c21aba633d1c49/screenshots/Windows/textbox_test.png -------------------------------------------------------------------------------- /screenshots/Windows/window_test.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KISS-UI/kiss-ui/074bf82df1b90739bd0c6df094c21aba633d1c49/screenshots/Windows/window_test.png -------------------------------------------------------------------------------- /src/attrs.rs: -------------------------------------------------------------------------------- 1 | c_str_consts! { 2 | //Globals 3 | UTF8_MODE = "UTF8MODE", 4 | 5 | // Basic widget attributes 6 | TITLE = "TITLE", 7 | VALUE = "VALUE", 8 | ACTIVE = "ACTIVE", 9 | NAME = "NAME", 10 | VISIBLE = "VISIBLE", 11 | 12 | // Rendering attributes 13 | RASTERSIZE = "RASTERSIZE", 14 | POSITION = "POSITION", 15 | 16 | // Layout attributes 17 | ALIGNMENT_VERT = "ALIGNMENTLIN", 18 | ALIGNMENT_HORI = "ALIGNMENTCOL", 19 | ORIENTATION = "ORIENTATION", 20 | NUMDIV = "numdiv", 21 | 22 | // Specific to `Absolute` 23 | CX = "CX", 24 | CY = "CY", 25 | 26 | //Textbox attributes 27 | MULTILINE = "MULTILINE", 28 | VISIBLE_COLUMNS = "VISIBLECOLUMNS", 29 | VISIBLE_LINES = "VISIBLELINES", 30 | 31 | // Progressbar attributes 32 | DASHED = "DASHED", 33 | MARQUEE = "MARQUEE", 34 | MIN = "MIN", 35 | MAX = "MAX", 36 | 37 | //Timer attribute 38 | TIME = "TIME", 39 | RUN = "RUN", 40 | 41 | // Spacing between elements in a container 42 | GAP = "GAP", 43 | 44 | // Handles 45 | IMAGE = "IMAGE", 46 | 47 | //Callbacks 48 | ACTION = "ACTION", 49 | ACTION_CB = "ACTION_CB", 50 | VALUE_CHANGED_CB = "VALUECHANGED_CB", 51 | MAP_CB = "MAP_CB", 52 | } 53 | 54 | pub mod values { 55 | c_str_consts! { 56 | YES = "YES", 57 | NO = "NO", 58 | } 59 | 60 | pub fn bool_yes_no(_bool: bool) -> &'static str { 61 | match _bool { 62 | true => YES, 63 | false => NO, 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/base.rs: -------------------------------------------------------------------------------- 1 | //! A general widget type that can be specialized at runtime. 2 | 3 | use widget_prelude::*; 4 | 5 | use ::KISSContext; 6 | 7 | use std::borrow::Borrow; 8 | 9 | /// A general widget type that can be specialized at runtime via `Downcast`. 10 | pub struct BaseWidget(IUPPtr); 11 | 12 | impl BaseWidget { 13 | /// Attempt to load a widget named by `name` from internal storage. 14 | /// 15 | /// If successful, the `BaseWidget` can then be downcast to the original widget type. 16 | /// 17 | /// Returns `None` if no widget by that name was found. 18 | /// 19 | /// ##Panics 20 | /// If called before `kiss_ui::show_gui()` is invoked or after it returns. 21 | pub fn load>(name: N) -> Option { 22 | KISSContext::load_widget(&name) 23 | } 24 | 25 | /// Attempt to downcast this `BaseWidget` to a more specialized widget type. 26 | /// 27 | /// This will return an error if the underlying widget class is different than the one 28 | /// it is being cast to. 29 | pub fn try_downcast(self) -> Result where T: Downcast { 30 | T::try_downcast(self) 31 | } 32 | } 33 | 34 | impl_widget! { BaseWidget } 35 | 36 | /// A trait describing a widget's ability to be downcast from `BaseWidget`. 37 | pub trait Downcast: Widget { 38 | /// Attempt to downcast `base` to the `Self` type, 39 | /// returning `Err(base)` if unsuccessful. 40 | fn try_downcast(base: BaseWidget) -> Result { 41 | if Self::can_downcast(&base) { 42 | Ok(unsafe { Self::downcast(base) }) 43 | } else { 44 | Err(base) 45 | } 46 | } 47 | 48 | // These are not meant for end-users to call. 49 | // They are an implementation detail of `try_downcast()`. 50 | #[doc(hidden)] 51 | unsafe fn downcast(base: BaseWidget) -> Self { 52 | Self::from_ptr(base.ptr()) 53 | } 54 | 55 | #[doc(hidden)] 56 | fn can_downcast(base: &BaseWidget) -> bool; 57 | } 58 | 59 | -------------------------------------------------------------------------------- /src/button.rs: -------------------------------------------------------------------------------- 1 | //! Buttons that can receive user input. 2 | 3 | use widget_prelude::*; 4 | 5 | use std::ptr; 6 | 7 | /// A button that can be clicked momentarily and invoke a callback when this happens. 8 | pub struct Button(IUPPtr); 9 | 10 | impl Button { 11 | /// Create a new `Button` with no label. 12 | pub fn new() -> Button { 13 | unsafe { 14 | let ptr = ::iup_sys::IupButton(ptr::null(), ptr::null()); 15 | Self::from_ptr(ptr) 16 | } 17 | } 18 | 19 | /// Set the label of this button. Can be blank. 20 | pub fn set_label>(self, label: L) -> Self { 21 | self.set_str_attribute(::attrs::TITLE, label); 22 | self 23 | } 24 | } 25 | 26 | impl_widget! { Button, "button" } 27 | 28 | impl_onclick! { Button } 29 | 30 | impl ::image::ImageContainer for Button {} 31 | -------------------------------------------------------------------------------- /src/callback.rs: -------------------------------------------------------------------------------- 1 | //! Traits for notifying client code when the state of a KISS-UI widget is updated. 2 | 3 | use widget_prelude::*; 4 | 5 | use iup_sys::Ihandle; 6 | 7 | use std::cell::RefCell; 8 | use std::collections::HashMap; 9 | 10 | /// Set this within a callback to tell the framework if it should close or not. 11 | /// 12 | /// If `Callback::close.set()` is called within a callback, then when the callback returns, 13 | /// the dialog containing the widget on which the callback was invoked will be closed. 14 | #[derive(Copy, Clone, PartialEq, Eq)] 15 | pub enum CallbackStatus { 16 | //Ignore, 17 | /// The default `CallbackStatus`, does nothing when set. 18 | Default, 19 | /// If this is set within a callback, then when the callback returns the dialog containing the 20 | /// widget on which the callback was invoked will be closed. 21 | Close, 22 | //Continue, 23 | } 24 | 25 | impl CallbackStatus { 26 | pub fn close(&mut self) { 27 | *self = CallbackStatus::Close; 28 | } 29 | 30 | #[doc(hidden)] 31 | pub fn to_cb_return(self) -> ::libc::c_int { 32 | use self::CallbackStatus::*; 33 | 34 | match self { 35 | Close => ::iup_sys::IUP_CLOSE, 36 | Default => ::iup_sys::IUP_DEFAULT, 37 | // _ => unimplemented!(), 38 | } 39 | } 40 | } 41 | 42 | impl From<()> for CallbackStatus { 43 | fn from(_: ()) -> CallbackStatus { 44 | CallbackStatus::Default 45 | } 46 | } 47 | 48 | pub trait Callback: 'static { 49 | fn on_callback(&mut self, args: Args) -> CallbackStatus; 50 | } 51 | 52 | impl, F: 'static> Callback for F where F: FnMut(Args) -> Out { 53 | /// Because of the `impl From<()> for CallbackStatus`, closures that return `()` can be 54 | /// accepted by this impl. 55 | fn on_callback(&mut self, args: Args) -> CallbackStatus { 56 | self(args).into() 57 | } 58 | } 59 | 60 | #[doc(hidden)] 61 | pub type CallbackMap = RefCell>>>; 62 | 63 | macro_rules! callback_impl { 64 | ($cb_attr:expr, $base:expr, $callback:expr, $self_ty:ident) => ( 65 | { 66 | thread_local!( 67 | static CALLBACKS: ::callback::CallbackMap<$self_ty> = 68 | ::std::cell::RefCell::new(::std::collections::HashMap::new()) 69 | ); 70 | 71 | extern fn extern_callback(element: *mut ::iup_sys::Ihandle) 72 | -> ::libc::c_int { 73 | use ::callback::CallbackStatus; 74 | 75 | let widget = unsafe { $self_ty::from_ptr(element) }; 76 | 77 | CALLBACKS.with(|callbacks| 78 | callbacks.borrow_mut() 79 | .get_mut(&widget.ptr()) 80 | .map(|cb| cb.on_callback(widget)) 81 | ).unwrap_or(CallbackStatus::Default).to_cb_return() 82 | } 83 | 84 | CALLBACKS.with(|callbacks| 85 | callbacks.borrow_mut().insert($base.ptr(), Box::new($callback)) 86 | ); 87 | $base.set_callback($cb_attr, extern_callback); 88 | } 89 | ) 90 | } 91 | 92 | /// A trait describing a widget that can be clicked, and can notify client code when this occurs. 93 | pub trait OnClick: Widget { 94 | fn set_onclick(self, on_click: Cb) -> Self where Cb: Callback; 95 | } 96 | 97 | macro_rules! impl_onclick { 98 | ($self_ty:ident) => ( 99 | impl $crate::callback::OnClick for $self_ty { 100 | fn set_onclick(self, on_click: Cb) -> Self where Cb: ::callback::Callback { 101 | callback_impl! { $crate::attrs::ACTION, self, on_click, $self_ty } 102 | self 103 | } 104 | } 105 | ) 106 | } 107 | 108 | /// A trait describing a widget which has a value that can be changed by the user, and can notify 109 | /// client code when this occurs. 110 | pub trait OnValueChange: Widget { 111 | fn set_on_value_changed(self, on_value_chaged: Cb) -> Self where Cb: Callback; 112 | } 113 | 114 | macro_rules! impl_on_value_change { 115 | ($self_ty:ident) => ( 116 | impl $crate::callback::OnValueChange for $self_ty { 117 | fn set_on_value_changed(self, on_value_changed: Cb) -> Self where Cb: ::callback::Callback { 118 | callback_impl! { $crate::attrs::VALUE_CHANGED_CB, self, on_value_changed, $self_ty } 119 | self 120 | } 121 | } 122 | ) 123 | } 124 | 125 | /// A trait describing a widget that can be shown, and can notify client code when this occurs. 126 | pub trait OnShow: Widget { 127 | fn set_on_show(self, on_show: Cb) -> Self where Cb: Callback; 128 | } 129 | 130 | macro_rules! impl_on_show { 131 | ($self_ty:ident) => ( 132 | impl ::callback::OnShow for $self_ty { 133 | fn set_on_show(self, on_show: Cb) -> Self where Cb: ::callback::Callback { 134 | callback_impl! { ::attrs::MAP_CB, self, on_show, $self_ty } 135 | self 136 | } 137 | } 138 | ) 139 | } 140 | -------------------------------------------------------------------------------- /src/container.rs: -------------------------------------------------------------------------------- 1 | //! Assorted types that can contain multiple widgets. 2 | //! 3 | //! All container types can be nested. 4 | //! 5 | //! Use the `children!{}` macro in this crate to convert a heterogeneous list of widgets into a 6 | //! `Vec` for the container constructors. 7 | 8 | use base::BaseWidget; 9 | use widget_prelude::*; 10 | 11 | /// Vertical alignment setting, used by `Horizontal` and `Grid`. 12 | #[derive(Copy, Clone)] 13 | pub enum VAlign { 14 | Top, 15 | Center, 16 | Bottom, 17 | } 18 | 19 | impl VAlign { 20 | fn as_cstr(self) -> &'static str { 21 | use self::VAlign::*; 22 | 23 | match self { 24 | Top => cstr!("ATOP"), 25 | Center => cstr!("ACENTER"), 26 | Bottom => cstr!("ABOTTOM"), 27 | } 28 | } 29 | } 30 | 31 | /// Horizontal alignment setting, used by `Vertical` and `Grid`. 32 | #[derive(Copy, Clone)] 33 | pub enum HAlign { 34 | Left, 35 | Center, 36 | Right, 37 | } 38 | 39 | impl HAlign { 40 | fn as_cstr(self) -> &'static str { 41 | use self::HAlign::*; 42 | 43 | match self { 44 | Left => cstr!("ALEFT"), 45 | Center => cstr!("ACENTER"), 46 | Right => cstr!("ARIGHT"), 47 | } 48 | } 49 | } 50 | 51 | /// The behavior of this enum depends on its point of use. 52 | #[derive(PartialEq, Eq, Copy, Clone)] 53 | pub enum Orientation { 54 | Vertical, 55 | Horizontal, 56 | } 57 | 58 | impl Orientation { 59 | #[doc(hidden)] 60 | pub fn as_cstr(self) -> &'static str { 61 | use self::Orientation::*; 62 | 63 | match self { 64 | Vertical => cstr!("VERTICAL"), 65 | Horizontal => cstr!("HORIZONTAL"), 66 | } 67 | } 68 | } 69 | 70 | 71 | fn raw_handle_vec(widgets: B) -> Vec where B: AsRef<[BaseWidget]> { 72 | let mut raw_handles: Vec<_> = widgets.as_ref().iter().cloned().map(BaseWidget::ptr).collect(); 73 | raw_handles.push(::std::ptr::null_mut()); 74 | raw_handles 75 | } 76 | 77 | /// A builder for `Absolute`, used to create and add children. 78 | pub struct AbsoluteBuilder { 79 | handles: Vec, 80 | } 81 | 82 | impl AbsoluteBuilder { 83 | fn new() -> AbsoluteBuilder { 84 | AbsoluteBuilder { handles: Vec::new() } 85 | } 86 | 87 | /// Add a child to the `Absolute` at the given coordinates, relative to the top-left corner of 88 | /// the container. 89 | pub fn add_child_at(&mut self, x: u32, y: u32, child: W) -> &mut Self { 90 | Absolute::set_child_pos(x, y, child); 91 | self.handles.push(child.ptr()); 92 | self 93 | } 94 | } 95 | 96 | 97 | /// A container type that makes no effort to arrange its children. Instead, they must be positioned 98 | /// manually. 99 | pub struct Absolute(IUPPtr); 100 | 101 | impl Absolute { 102 | /// Create a new absolute container using the given closure, which will be passed a mutable builder 103 | /// instance. 104 | /// 105 | /// The builder is necessary to ensure that the children have their positions set correctly, as 106 | /// `Widget::set_position` will not set the attributes that this particular container is 107 | /// expecting. 108 | pub fn new(build_fn: F) -> Absolute where F: FnOnce(&mut AbsoluteBuilder) { 109 | let mut builder = AbsoluteBuilder::new(); 110 | build_fn(&mut builder); 111 | 112 | unsafe { 113 | let ptr = ::iup_sys::IupCboxv(builder.handles.as_mut_ptr()); 114 | Self::from_ptr(ptr) 115 | } 116 | } 117 | 118 | /// Set the position of a child of an `Absolute` relative to its top-left corner. 119 | pub fn set_child_pos(x: u32, y: u32, child: W) { 120 | child.set_int_attribute(::attrs::CX, x as i32); 121 | child.set_int_attribute(::attrs::CY, y as i32); 122 | } 123 | } 124 | 125 | impl_widget! { Absolute, "cbox" } 126 | 127 | /// A container widget that lines up its children from left to right. 128 | pub struct Horizontal(IUPPtr); 129 | 130 | impl Horizontal { 131 | /// Create a new horizontal container with the given vector or array of children, which may 132 | /// also be empty. 133 | /// 134 | /// See the `children![]` macro in this crate for more info. 135 | pub fn new(children: C) -> Horizontal where C: AsRef<[BaseWidget]> { 136 | let mut raw_handles = raw_handle_vec(children); 137 | 138 | unsafe { 139 | let ptr = ::iup_sys::IupHboxv(raw_handles.as_mut_ptr()); 140 | Self::from_ptr(ptr) 141 | } 142 | } 143 | 144 | pub fn set_valign(self, valign: VAlign) -> Self { 145 | self.set_const_str_attribute(::attrs::ALIGNMENT_VERT, valign.as_cstr()); 146 | self 147 | } 148 | 149 | pub fn set_elem_spacing_pixels(self, spacing: u32) -> Self { 150 | self.set_str_attribute(::attrs::GAP, spacing.to_string()); 151 | self 152 | } 153 | } 154 | 155 | impl_widget! { Horizontal, "hbox" } 156 | 157 | /// A container widget that lines up its children from top to bottom. 158 | pub struct Vertical(IUPPtr); 159 | 160 | impl Vertical { 161 | pub fn new(children: C) -> Vertical where C: AsRef<[BaseWidget]> { 162 | let mut raw_handles = raw_handle_vec(children); 163 | 164 | unsafe { 165 | let ptr = ::iup_sys::IupVboxv(raw_handles.as_mut_ptr()); 166 | Self::from_ptr(ptr) 167 | } 168 | } 169 | 170 | pub fn set_halign(self, halign: HAlign) -> Self { 171 | self.set_const_str_attribute(::attrs::ALIGNMENT_HORI, halign.as_cstr()); 172 | self 173 | } 174 | 175 | pub fn set_elem_spacing_pixels(self, spacing: u32) -> Self { 176 | self.set_str_attribute(::attrs::GAP, spacing.to_string()); 177 | self 178 | } 179 | } 180 | 181 | 182 | impl_widget! { Vertical, "vbox" } 183 | 184 | /// A container widget that lines up its children from left to right, and from top to bottom. 185 | pub struct Grid(IUPPtr); 186 | 187 | impl Grid { 188 | pub fn new(children: C) -> Grid where C: AsRef<[BaseWidget]> { 189 | let mut raw_handles = raw_handle_vec(children); 190 | 191 | unsafe { 192 | let ptr = ::iup_sys::IupGridBoxv(raw_handles.as_mut_ptr()); 193 | Self::from_ptr(ptr) 194 | } 195 | } 196 | pub fn set_valign(self, valign: VAlign) -> Self { 197 | self.set_const_str_attribute(::attrs::ALIGNMENT_VERT, valign.as_cstr()); 198 | self 199 | } 200 | 201 | pub fn set_halign(self, halign: HAlign) -> Self { 202 | self.set_const_str_attribute(::attrs::ALIGNMENT_HORI, halign.as_cstr()); 203 | self 204 | } 205 | 206 | /// Based on the orientation, set the number of children to place in a: 207 | /// 208 | /// * `Vertical`: **column** 209 | /// * `Horizontal`: **row** 210 | /// 211 | /// before beginning the next one. 212 | pub fn set_ndiv(self, ndiv: u32) -> Self { 213 | self.set_int_attribute(::attrs::NUMDIV, ndiv as i32); 214 | self 215 | } 216 | 217 | /// Sets how children are distributed in the container. 218 | /// 219 | /// * `Vertical`: The container will fill columns first. 220 | /// 221 | /// Visual example (`ndiv=3` grid with 7 children): 222 | /// 223 | /// 224 | /// 225 | /// 226 | /// 227 | /// 228 | /// 229 | /// 230 | /// 231 | /// 232 | /// 233 | /// 234 | /// 235 | /// 236 | ///
ChildChildChild
ChildChild
ChildChild
237 | /// 238 | /// * `Horizontal`: The container will fill rows first. **Default.** 239 | /// 240 | /// Visual example (`ndiv=3` grid with 7 children): 241 | /// 242 | /// 243 | /// 244 | /// 245 | /// 246 | /// 247 | /// 248 | /// 249 | /// 250 | /// 251 | /// 252 | /// 253 | /// 254 | /// 255 | ///
ChildChildChild
ChildChildChild
Child
256 | /// 257 | pub fn set_orientation(&mut self, orientation: Orientation) -> &mut Self { 258 | self.set_const_str_attribute(::attrs::ORIENTATION, orientation.as_cstr()); 259 | self 260 | } 261 | } 262 | 263 | impl_widget! { Grid, "matrix" } 264 | 265 | /// Convert a heterogeneous list of widgets into a `Vec`, 266 | /// suitable for passing to any function that takes `AsRef<[BaseWidget]>`, such as a constructor 267 | /// for one of the container types. 268 | #[macro_export] 269 | macro_rules! children [ 270 | // Accepts invocation with or without a final comma. 271 | ($($child:expr),+,) => (children![$($child),+]); 272 | ($($child:expr),+) => ({ 273 | use ::kiss_ui::widget::Widget; 274 | vec![$($child.to_base()),+] 275 | }); 276 | () => (vec![]); 277 | ]; 278 | -------------------------------------------------------------------------------- /src/dialog.rs: -------------------------------------------------------------------------------- 1 | //! KISS-UI top-level dialogs (windows) 2 | 3 | use base::BaseWidget; 4 | use widget_prelude::*; 5 | 6 | use ::iup_sys; 7 | 8 | use std::ffi::CString; 9 | use std::ptr; 10 | 11 | /// A top-level dialog that can create a new native window when shown, 12 | /// and can contain a single widget (which can be a container for many widgets). 13 | pub struct Dialog(IUPPtr); 14 | 15 | impl Dialog { 16 | /// Create a new dialog with a single child. 17 | /// 18 | /// To create a dialog containing multiple widgets, use a struct from the `container` module. 19 | /// 20 | /// ##Note 21 | /// This does **not** make the dialog appear on screen. `.show()` must be called after the 22 | /// dialog has been configured. 23 | /// 24 | /// ##Panics 25 | /// If called outside a valid KISS-UI context. 26 | pub fn new(contents: W) -> Dialog where W: Widget { 27 | assert_kiss_running!(); 28 | 29 | unsafe { 30 | let ptr = iup_sys::IupDialog(contents.ptr()); 31 | Self::from_ptr(ptr) 32 | } 33 | } 34 | 35 | /// Create a new dialog with no children. 36 | /// 37 | /// ##Panics 38 | /// If called outside a valid KISS-UI context. 39 | pub fn empty() -> Dialog { 40 | assert_kiss_running!(); 41 | 42 | unsafe { 43 | let ptr = iup_sys::IupDialog(ptr::null_mut()); 44 | Self::from_ptr(ptr) 45 | } 46 | } 47 | 48 | /// Set the title of this dialog, which will appear in the title bar of the native window. 49 | pub fn set_title>(self, title: T) -> Self { 50 | self.set_str_attribute(::attrs::TITLE, title); 51 | self 52 | } 53 | 54 | /// Set the size of this dialog in pixels. 55 | pub fn set_size_pixels(self, width: u32, height: u32) -> Self { 56 | let rastersize = format!("{}x{}", width, height); 57 | self.set_str_attribute(::attrs::RASTERSIZE, rastersize); 58 | self 59 | } 60 | 61 | /// Get a child of this dialog named by `name`. 62 | /// 63 | /// Returns `None` if the child was not found. 64 | pub fn get_child(self, name: &str) -> Option { 65 | let name = CString::new(name).unwrap(); 66 | 67 | unsafe { 68 | let child_ptr = iup_sys::IupGetDialogChild(self.ptr(), name.as_ptr()); 69 | BaseWidget::from_ptr_opt(child_ptr) 70 | } 71 | } 72 | } 73 | 74 | impl Destroy for Dialog {} 75 | 76 | impl_widget! { Dialog, "dialog" } 77 | 78 | impl_on_show! { Dialog } 79 | 80 | /// Popup a message dialog and block until it is closed, by either the OK button or the exit 81 | /// button. 82 | pub fn message_popup, M: Into>(title: T, message: M) { 83 | assert_kiss_running!(); 84 | 85 | let title = CString::new(title.into()).unwrap(); 86 | let message = CString::new(message.into()).unwrap(); 87 | 88 | unsafe { 89 | iup_sys::IupMessage(title.as_ptr(), message.as_ptr()); 90 | } 91 | } 92 | 93 | /// A builder for an alert dialog that can show a message and up to 3 buttons for the user's 94 | /// response. 95 | pub struct AlertPopupBuilder { 96 | pub title: String, 97 | pub message: String, 98 | pub button1: String, 99 | pub button2: Option, 100 | pub button3: Option, 101 | } 102 | 103 | impl AlertPopupBuilder { 104 | pub fn new, M: Into, B1: Into>( 105 | title: T, message: M, button1: B1 106 | ) -> AlertPopupBuilder { 107 | AlertPopupBuilder { 108 | title: title.into(), 109 | message: message.into(), 110 | button1: button1.into(), 111 | button2: None, 112 | button3: None, 113 | } 114 | } 115 | 116 | /// Set the text of the second button 117 | pub fn button2>(mut self, button2: B2) -> Self { 118 | self.button2 = Some(button2.into()); 119 | self 120 | } 121 | 122 | pub fn button3>(mut self, button3: B3) -> Self { 123 | self.button3 = Some(button3.into()); 124 | self 125 | } 126 | 127 | /// Popup the dialog and block until the user takes an action. 128 | /// 129 | /// Returns: which button was pressed, or **0** if the dialog was closed. 130 | pub fn popup(self) -> i32 { 131 | let title = CString::new(self.title).unwrap(); 132 | let message = CString::new(self.message).unwrap(); 133 | let button1 = CString::new(self.button1).unwrap(); 134 | let button2 = self.button2.map(|b2| CString::new(b2).unwrap()); 135 | let button3 = self.button3.map(|b3| CString::new(b3).unwrap()); 136 | 137 | unsafe { 138 | iup_sys::IupAlarm( 139 | title.as_ptr(), 140 | message.as_ptr(), 141 | button1.as_ptr(), 142 | button2.as_ref().map_or_else(ptr::null, |b2| b2.as_ptr()), 143 | button3.as_ref().map_or_else(ptr::null, |b3| b3.as_ptr()), 144 | ) 145 | } 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /src/image.rs: -------------------------------------------------------------------------------- 1 | //! Renderable image buffers. 2 | 3 | use widget_prelude::*; 4 | 5 | use base::Downcast; 6 | 7 | use std::mem; 8 | 9 | /// An image buffer allocated by IUP. 10 | /// 11 | /// ##Note: Not a Renderable Widget 12 | /// While this type can be dereferenced and converted to `BaseWidget`, it is *not* a renderable 13 | /// widget and adding it to a container will have no visual effect. 14 | /// 15 | /// Instead, it should be set on another widget type that implements the `ImageContainer` trait, 16 | /// which will handle the actual rendering. 17 | /// 18 | /// ##Note: Memory Usage 19 | /// This struct should be freed by calling `.destroy()` on it when it is no longer in use. 20 | /// Otherwise, it will be freed when `kiss_ui::show_gui()` exits^([citation needed]). 21 | /// 22 | /// ##Note: Cloning 23 | /// Cloning this image does not duplicate its allocation. Thus, destroying one image cloned from 24 | /// another will destroy them both. 25 | pub struct Image(IUPPtr); 26 | 27 | impl Image { 28 | /// Create a new RGB image buffer from a slice of 3-byte tuples, copying the data into a new 29 | /// allocation. 30 | /// 31 | /// See `transmute_buffer_rgb()` in this module. 32 | /// 33 | /// ##Panics 34 | /// If `width * height` is not equal to `pixels.len()`. 35 | pub fn new_rgb(width: u32, height: u32, pixels: &[(u8, u8, u8)]) -> Image { 36 | assert_eq!((width * height) as usize, pixels.len()); 37 | unsafe { 38 | let ptr = ::iup_sys::IupImageRGB(width as i32, height as i32, pixels.as_ptr() as *const u8); 39 | Self::from_ptr(ptr) 40 | } 41 | } 42 | 43 | /// Create a new RGBA image buffer from a slice of 4-byte tuples, copying the data into a new 44 | /// allocation. 45 | /// 46 | /// See `transmute_buffer_rgba` in this module. 47 | /// 48 | /// ##Panics 49 | /// If `width * height` is not equal to `pixels.len()`. 50 | pub fn new_rgba(width: u32, height: u32, pixels: &[(u8, u8, u8, u8)]) -> Image { 51 | assert_eq!((width * height) as usize, pixels.len()); 52 | unsafe { 53 | let ptr = ::iup_sys::IupImageRGBA(width as i32, height as i32, pixels.as_ptr() as *const u8); 54 | Self::from_ptr(ptr) 55 | } 56 | } 57 | } 58 | 59 | impl Destroy for Image {} 60 | 61 | impl_widget! { Image, ["image", "imagergb", "imagergba"] } 62 | 63 | /// Cast a slice of bytes to a slice of 3-byte tuples without copying. 64 | /// 65 | /// Returns `None` if `buf.len()` is not evenly divisible by 3. 66 | pub fn transmute_buffer_rgb(buf: &[u8]) -> Option<&[(u8, u8, u8)]> { 67 | if buf.len() % 3 == 0 { 68 | Some(unsafe { mem::transmute(buf) }) 69 | } else { 70 | None 71 | } 72 | } 73 | 74 | /// Cast a slice of bytes to a slice of 4-byte tuples without copying. 75 | /// 76 | /// Returns `None` if `buf.len()` is not evenly divisible by 4. 77 | pub fn transmute_buffer_rgba(buf: &[u8]) -> Option<&[(u8, u8, u8, u8)]> { 78 | if buf.len() % 4 == 0 { 79 | Some(unsafe { mem::transmute(buf) }) 80 | } else { 81 | None 82 | } 83 | } 84 | 85 | /// A trait describing an object that can render an image within itself. 86 | pub trait ImageContainer: Widget { 87 | /// Set the image this widget is to render and return `self` for method chaining. 88 | fn set_image(self, image: Image) -> Self { 89 | self.set_attr_handle(::attrs::IMAGE, image); 90 | self 91 | } 92 | 93 | /// Get a copy of the image set on this widget, if any. 94 | fn get_image(&self) -> Option { 95 | use base::BaseWidget; 96 | 97 | self.get_attr_handle(::attrs::IMAGE) 98 | .map(BaseWidget::try_downcast::) 99 | .and_then(Result::ok) 100 | } 101 | } 102 | 103 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! A UI framework for Rust based on the KISS principle: "Keep It Simple, Stupid!" 2 | //! 3 | //! Built on top of the [IUP GUI library for C.][iup] 4 | //! 5 | //! ##Note: "valid KISS-UI context" 6 | //! All KISS-UI static widget methods will panic if called before `kiss_ui::show_gui()` is invoked or 7 | //! after it returns. 8 | //! 9 | //! This is because the underlying IUP library has been either, respectively, not initialized yet 10 | //! or already deinitialized, and attempting to interact with it in either situation will likely cause 11 | //! undefined behavior. 12 | //! 13 | //! ##Note: This is a (technically) leaky abstraction. 14 | //! Because IUP only frees all its allocations when it is deinitialized, all widgets created by KISS-UI 15 | //! will remain in-memory until `kiss_ui::show_gui()` returns. While unbounded memory growth can 16 | //! happen with complex applications, this should not be an issue for most use-cases. 17 | //! 18 | //! However, some types *do* allocate large chunks of memory, or other valuable system resources, 19 | //! and should be manually freed when they are no longer being used. 20 | //! This is most evident with the `Image` struct, which can allocate large backing buffers for image data. 21 | //! 22 | //! All types that should be manually freed expose a `.destroy()` method which should be called 23 | //! when they are no longer being used. This can safely be called multiple times on clones of the 24 | //! widget types^([citation needed]). 25 | //! 26 | //! [iup]: http://webserver2.tecgraf.puc-rio.br/iup/ 27 | 28 | extern crate libc; 29 | extern crate iup_sys; 30 | 31 | 32 | macro_rules! assert_kiss_running ( 33 | () => ( 34 | assert!( 35 | ::KISS_RUNNING.load(::std::sync::atomic::Ordering::Acquire), 36 | "No KISS-UI widget methods may be called before `kiss_ui::show_gui()` is invoked or after it returns!" 37 | ) 38 | ) 39 | ); 40 | 41 | #[macro_use] 42 | pub mod widget; 43 | 44 | #[macro_use] 45 | pub mod utils; 46 | 47 | // Internal use modules 48 | mod attrs; 49 | 50 | // User-facing modules 51 | #[macro_use] 52 | pub mod callback; 53 | 54 | pub mod base; 55 | pub mod button; 56 | pub mod container; 57 | pub mod dialog; 58 | pub mod image; 59 | pub mod progress; 60 | pub mod text; 61 | pub mod timer; 62 | 63 | use std::borrow::Borrow; 64 | use std::cell::{Cell, RefCell}; 65 | use std::collections::HashMap; 66 | use std::ptr; 67 | use std::rc::Rc; 68 | use std::sync::atomic::{AtomicBool, ATOMIC_BOOL_INIT, Ordering}; 69 | 70 | use base::BaseWidget; 71 | use dialog::Dialog; 72 | use widget::Widget; 73 | 74 | use utils::cstr::AsCStr; 75 | 76 | use widget_prelude::IUPPtr; 77 | 78 | mod widget_prelude { 79 | pub use widget::{Widget, IUPWidget, Destroy, WidgetStr}; 80 | pub type IUPPtr = *mut ::iup_sys::Ihandle; 81 | } 82 | 83 | /// A module that KISS-UI users can glob-import to get the most common types. 84 | pub mod prelude { 85 | pub use base::BaseWidget; 86 | pub use dialog::Dialog; 87 | pub use container::Orientation; 88 | pub use callback::{CallbackStatus, OnClick, OnShow, OnValueChange}; 89 | 90 | pub use widget::{Widget, Destroy}; 91 | } 92 | 93 | static KISS_RUNNING: AtomicBool = ATOMIC_BOOL_INIT; 94 | 95 | thread_local! { static CONTEXT: KISSContext = KISSContext::default() } 96 | 97 | #[derive(Default)] 98 | struct KISSContext { 99 | widget_store: RefCell>, 100 | // FIXME: use Rc<()> once Rc::is_unique stabilizes 101 | borrowed_strs: RefCell>>>>, 102 | } 103 | 104 | impl KISSContext { 105 | fn assert_str_not_borrowed(widget: IUPPtr, str_: &'static str) { 106 | assert_kiss_running!(); 107 | 108 | let is_borrowed = CONTEXT.with(|context| 109 | context.borrowed_strs.borrow() 110 | .get(&widget) 111 | .and_then(|widget_strs| 112 | widget_strs.get(str_) 113 | .map(|refcount| refcount.get() != 0) 114 | ) 115 | .unwrap_or(false) 116 | ); 117 | 118 | assert!( 119 | !is_borrowed, 120 | "Cannot update the value of a string property of a widget if it's been previously borrowed!" 121 | ); 122 | } 123 | 124 | fn str_refcount(widget: IUPPtr, str_: &'static str) -> Rc> { 125 | assert_kiss_running!(); 126 | 127 | CONTEXT.with(|context| 128 | context.borrowed_strs.borrow_mut() 129 | .entry(widget).or_insert_with(HashMap::new) 130 | .entry(str_).or_insert_with(|| Rc::new(Cell::new(0))) 131 | .clone() 132 | ) 133 | } 134 | 135 | fn store_widget, W: Widget>(name: N, widget: W) -> Option { 136 | CONTEXT.with(|context| 137 | context.widget_store.borrow_mut() 138 | .insert(name.into(), widget.to_base()) 139 | ) 140 | } 141 | 142 | fn load_widget>(name: &N) -> Option { 143 | CONTEXT.with(|context| 144 | context.widget_store.borrow().get(name.borrow()).cloned() 145 | ) 146 | } 147 | 148 | unsafe fn clear() { 149 | CONTEXT.with(|context| { 150 | context.widget_store.borrow_mut().clear(); 151 | context.borrowed_strs.borrow_mut().clear(); 152 | }) 153 | } 154 | } 155 | 156 | 157 | /// The entry point for KISS-UI. The closure argument should initialize and call `.show()`. 158 | /// 159 | /// ##Blocks 160 | /// Until all KISS-UI dialogs are closed. 161 | /// 162 | /// ##Warning 163 | /// No static widget methods from this crate may be called before this function is 164 | /// invoked or after it returns, with the exception of the closure passed to this function. 165 | /// 166 | /// While this function is blocked and the IUP event loop is running, any reachable code is 167 | /// considered a "valid KISS-UI context" and may create and interact with widgets and dialogs. 168 | /// 169 | /// After it returns, IUP is deinitialized and all static widget methods will panic to avoid 170 | /// undefined behavior. 171 | /// 172 | /// ##Note: `Send` bound 173 | /// This closure will be called in the same thread where `show_gui()` is invoked. No threading is 174 | /// involved. 175 | /// 176 | /// However, without the `Send` bound it would be possible to move widget types outside 177 | /// of the closure with safe code and interact with them after IUP has been deinitialized, 178 | /// which would cause undefined behavior. 179 | /// 180 | /// Since no widget types are `Send`, this bound prevents this from happening without requiring 181 | /// all widget methods to check if they were invoked in a valid context. 182 | pub fn show_gui(init_fn: F) where F: FnOnce() -> Dialog + Send { 183 | assert!( 184 | !KISS_RUNNING.compare_and_swap(false, true, Ordering::SeqCst), 185 | "KISS-UI may only be running (in `kiss_ui::show_gui()`) in one thread at a time!" 186 | ); 187 | 188 | unsafe { 189 | assert!(iup_sys::IupOpen(ptr::null(), ptr::null()) == 0); 190 | // Force IUP to always use UTF-8 191 | iup_sys::IupSetGlobal(::attrs::UTF8_MODE.as_cstr(), ::attrs::values::YES.as_cstr()); 192 | } 193 | 194 | init_fn().show(); 195 | 196 | unsafe { 197 | iup_sys::IupMainLoop(); 198 | iup_sys::IupClose(); 199 | KISSContext::clear(); 200 | } 201 | 202 | KISS_RUNNING.store(false, Ordering::SeqCst); 203 | } 204 | -------------------------------------------------------------------------------- /src/progress.rs: -------------------------------------------------------------------------------- 1 | //! Progress bars and dialogs. 2 | 3 | use widget_prelude::*; 4 | 5 | use container::Orientation; 6 | 7 | /// A widget that renders a bar which fills as its set value approaches a maximum. 8 | /// 9 | /// For more info, see the [`IupProgressBar`][iup-progress] documentation. (Note: "marquee" is the 10 | /// same as "indefinite") 11 | /// 12 | /// [iup-progress]: http://webserver2.tecgraf.puc-rio.br/iup/en/elem/iupprogressbar.html 13 | pub struct ProgressBar(IUPPtr); 14 | 15 | impl ProgressBar { 16 | /// Create a new progress bar. 17 | pub fn new() -> ProgressBar { 18 | unsafe { 19 | let ptr = ::iup_sys::IupProgressBar(); 20 | Self::from_ptr(ptr) 21 | } 22 | } 23 | 24 | /// Set this progress bar as indefinite or not. 25 | /// 26 | /// In the indefinite state, the progress bar will not 27 | /// show its true value; instead it will render a looping animation. 28 | /// 29 | /// This may not have a visual effect on certain platforms. 30 | pub fn set_indefinite(self, is_indefinite: bool) -> Self { 31 | self.set_bool_attribute(::attrs::MARQUEE, is_indefinite); 32 | self 33 | } 34 | 35 | /// Set if the progress bar should render solid (`false`) or dashed (`true`). 36 | /// 37 | /// This may not have a visual effect on certain platforms. 38 | pub fn set_dashed(self, dashed: bool) -> Self { 39 | self.set_bool_attribute(::attrs::DASHED, dashed); 40 | self 41 | } 42 | 43 | /// Set the maximum value of this progress bar, i.e. the value at which it will show full. 44 | /// 45 | /// Defaults to `1.0`. 46 | pub fn set_max(self, max: f32) -> Self { 47 | self.set_float_attribute(::attrs::MAX, max); 48 | self 49 | } 50 | 51 | /// Set the minimum value of this progress bar, i.e. the value at which it will be empty. 52 | /// 53 | /// Defaults to `0.0`. 54 | pub fn set_min(self, min: f32) -> Self { 55 | self.set_float_attribute(::attrs::MIN, min); 56 | self 57 | } 58 | 59 | /// Set the orientation of this progress bar. 60 | /// 61 | /// * `Vertical`: The progress bar will render as a vertical bar, and fill from bottom to top. 62 | /// * `Horizontal`: The progress bar will render as a horizontal bar, and fill from left to 63 | /// right. 64 | pub fn set_orientation(self, orientation: Orientation) -> Self { 65 | self.set_const_str_attribute(::attrs::ORIENTATION, orientation.as_cstr()); 66 | self 67 | } 68 | 69 | /// Set the current value of this progress bar. Its rendered infill will be updated to reflect 70 | /// the new value in relation to the minimum and maximum. 71 | pub fn set_value(self, val: f32) -> Self { 72 | self.set_float_attribute(::attrs::VALUE, val); 73 | self 74 | } 75 | 76 | /// Get the current value. 77 | pub fn get_value(self) -> f32 { 78 | self.get_float_attribute(::attrs::VALUE) 79 | } 80 | 81 | /// Add `amt` to the current value and update it. `amt` may be negative. 82 | pub fn add_value(self, amt: f32) -> Self { 83 | let val = self.get_float_attribute(::attrs::VALUE); 84 | self.set_float_attribute(::attrs::VALUE, val + amt); 85 | self 86 | } 87 | } 88 | 89 | impl_widget! { ProgressBar, "progressbar" } 90 | 91 | -------------------------------------------------------------------------------- /src/text.rs: -------------------------------------------------------------------------------- 1 | //! Widgets that can render and process text (labels, text boxes). 2 | 3 | use widget_prelude::*; 4 | 5 | use std::ffi::CString; 6 | use std::ptr; 7 | 8 | /// A static widget that renders text within its parent. 9 | pub struct Label(IUPPtr); 10 | 11 | impl Label { 12 | /// Create a label with some text. 13 | pub fn new>(text: S) -> Label { 14 | let c_text = CString::new(text.into()).unwrap(); 15 | unsafe { 16 | let ptr = ::iup_sys::IupLabel(c_text.as_ptr()); 17 | Self::from_ptr(ptr) 18 | } 19 | } 20 | 21 | /// Create a blank label. The text can be set later. 22 | pub fn new_empty() -> Label { 23 | unsafe { 24 | let ptr = ::iup_sys::IupLabel(ptr::null()); 25 | Self::from_ptr(ptr) 26 | } 27 | } 28 | 29 | /// Update the text of this label. 30 | /// 31 | /// ##Panics 32 | /// If any `WidgetStr` instances from `self.get_text()` are still reachable. 33 | pub fn set_text(self, text: &str) -> Self { 34 | self.set_str_attribute(::attrs::TITLE, text); 35 | self 36 | } 37 | 38 | /// Get the text of this label. 39 | pub fn get_text(&self) -> WidgetStr { 40 | self.get_str_attribute(::attrs::TITLE) 41 | .expect("This widget should have a text pointer even if it's empty!") 42 | } 43 | } 44 | 45 | impl_widget! { Label, "label" } 46 | 47 | impl ::image::ImageContainer for Label {} 48 | 49 | /// A widget that renders user-editable text. 50 | pub struct TextBox(IUPPtr); 51 | 52 | impl TextBox { 53 | /// Create a new, empty text box. 54 | pub fn new() -> TextBox { 55 | unsafe { 56 | let ptr = ::iup_sys::IupText(ptr::null()); 57 | Self::from_ptr(ptr) 58 | } 59 | } 60 | 61 | /// Set if the text box should accept and render newline characters. 62 | /// 63 | /// If `false`, it will only be slightly taller than a line of text in the current font. 64 | /// If `true`, the total dimensions will be set by `set_visible_columns` and 65 | /// `set_visible_lines`. Text outside these bounds will be accessible with a scrollbar. 66 | pub fn set_multiline(self, multiline: bool) -> Self { 67 | self.set_bool_attribute(::attrs::MULTILINE, multiline); 68 | self 69 | } 70 | 71 | /// Set the rendered width of the textbox in columns (character width + padding). 72 | /// 73 | /// If the textbox is set as multiline, this will cause additional text beyond the maximum 74 | /// width to wrap. Otherwise, it can be scrolled only horizontally. 75 | pub fn set_visible_columns(self, cols: u32) -> Self { 76 | self.set_int_attribute(::attrs::VISIBLE_COLUMNS, cols as i32); 77 | self 78 | } 79 | 80 | /// Set the rendered height of the textbox in lines (character height + padding). 81 | /// 82 | /// If the textbox is set as multiline, newline characters will push text following them to the 83 | /// next visible line. Line counts beyond these bounds will cause a scrollbar to be shown. 84 | pub fn set_visible_lines(self, lines: u32) -> Self { 85 | self.set_int_attribute(::attrs::VISIBLE_LINES, lines as i32); 86 | self 87 | } 88 | 89 | /// Set the text of this textbox. 90 | /// 91 | /// ##Panics 92 | /// If any `WidgetStr` instances from `self.get_text()` are still reachable. 93 | pub fn set_text(self, value: &str) -> Self { 94 | self.set_str_attribute(::attrs::VALUE, value); 95 | self 96 | } 97 | 98 | /// Get the text value of this textbox. 99 | pub fn get_text(&self) -> WidgetStr { 100 | self.get_str_attribute(::attrs::VALUE) 101 | .expect("This string should be present even if it's empty!") 102 | } 103 | } 104 | 105 | impl_widget! { TextBox, "text" } 106 | 107 | impl_on_value_change! { TextBox } 108 | 109 | -------------------------------------------------------------------------------- /src/timer.rs: -------------------------------------------------------------------------------- 1 | //! Timers that can invoke a callback on an interval. 2 | use widget_prelude::*; 3 | use ::callback::Callback; 4 | 5 | /// A timer that can invoke a callback on a configurable interval. 6 | /// 7 | /// ##Note: Not a Renderable Widget 8 | /// While this type can be dereferenced and converted to `BaseWidget`, it is *not* a renderable 9 | /// widget and adding it to a container will have no visual effect. 10 | /// 11 | /// ##Note: Resource Usage 12 | /// This struct should be freed by calling `.destroy()` on it when it is no longer in use to free 13 | /// any resources it has allocated. Otherwise, it will be freed when `kiss_ui::show_gui()` returns. 14 | pub struct Timer(IUPPtr); 15 | 16 | impl Timer { 17 | /// Create a new timer with a default interval. 18 | /// 19 | /// TODO: Document default interval. 20 | pub fn new() -> Timer { 21 | unsafe { 22 | let ptr = ::iup_sys::IupTimer(); 23 | Self::from_ptr(ptr) 24 | } 25 | } 26 | 27 | /// Set the timer interval in milliseconds. 28 | pub fn set_interval(self, time: u32) -> Self { 29 | self.set_int_attribute(::attrs::TIME, time as i32); 30 | self 31 | } 32 | 33 | /// Set a callback to be invoked when the timer interval elapses. 34 | /// The callback will be invoked on every interval until `.stop()` is called. 35 | pub fn set_on_interval(self, on_interval: Cb) -> Self where Cb: Callback { 36 | callback_impl! { ::attrs::ACTION_CB, self, on_interval, Timer } 37 | self 38 | } 39 | 40 | /// Start the timer. The callback will be invoked when the next interval elapses. 41 | pub fn start(self) -> Self { 42 | self.set_bool_attribute(::attrs::RUN, true); 43 | self 44 | } 45 | 46 | /// Stop the timer. The callback will not be invoked until the timer is restarted. 47 | pub fn stop(self) -> Self { 48 | self.set_bool_attribute(::attrs::RUN, false); 49 | self 50 | } 51 | } 52 | 53 | impl_widget! { Timer, "timer" } 54 | 55 | impl Destroy for Timer {} 56 | 57 | -------------------------------------------------------------------------------- /src/utils/cstr.rs: -------------------------------------------------------------------------------- 1 | pub trait AsCStr { 2 | fn as_cstr(&self) -> *const ::libc::c_char; 3 | } 4 | 5 | impl AsCStr for &'static str { 6 | fn as_cstr(&self) -> *const ::libc::c_char { 7 | let bytes = self.as_bytes(); 8 | assert!(bytes[bytes.len() - 1] == 0u8); 9 | bytes.as_ptr() as *const ::libc::c_char 10 | } 11 | } 12 | 13 | macro_rules! cstr ( 14 | ($val:expr) => ( 15 | concat!($val, "\0") 16 | ) 17 | ); 18 | 19 | macro_rules! c_str_const ( 20 | ($name:ident = $val:expr) => ( 21 | pub const $name: &'static str = cstr!($val); 22 | ) 23 | ); 24 | 25 | macro_rules! c_str_consts { 26 | ($($name:ident = $val:expr),+,) => ( 27 | $(c_str_const!($name = $val);)+ 28 | ) 29 | } 30 | -------------------------------------------------------------------------------- /src/utils/mod.rs: -------------------------------------------------------------------------------- 1 | //! Utility types and functions that might be useful for KISS-UI users. 2 | 3 | #[doc(hidden)] 4 | #[macro_use] 5 | pub mod cstr; 6 | pub mod move_cell; 7 | -------------------------------------------------------------------------------- /src/utils/move_cell.rs: -------------------------------------------------------------------------------- 1 | //! A cell type that can move values into and out of a shared reference. 2 | //! 3 | //! Behaves like `RefCell>` but optimized for use-cases where temporary or permanent 4 | //! ownership is required. 5 | 6 | use std::cell::UnsafeCell; 7 | use std::mem; 8 | 9 | /// A cell type that can move values into and out of a shared reference. 10 | pub struct MoveCell(UnsafeCell>); 11 | 12 | impl MoveCell { 13 | /// Create a new `MoveCell` with no contained value. 14 | pub fn new() -> MoveCell { 15 | MoveCell(UnsafeCell::new(None)) 16 | } 17 | 18 | /// Create a new `MoveCell` with the given value. 19 | pub fn with(val: T) -> MoveCell { 20 | MoveCell(UnsafeCell::new(Some(val))) 21 | } 22 | 23 | /// Create a new `MoveCell` around the given `Option`. 24 | pub fn from(opt: Option) -> MoveCell { 25 | MoveCell(UnsafeCell::new(opt)) 26 | } 27 | 28 | unsafe fn as_mut(&self) -> &mut Option { 29 | &mut *self.0.get() 30 | } 31 | 32 | unsafe fn as_ref(&self) -> &Option { 33 | & *self.0.get() 34 | } 35 | 36 | /// Place a value into this `MoveCell`, returning the previous value, if present. 37 | pub fn put(&self, val: T) -> Option { 38 | mem::replace(unsafe { self.as_mut() }, Some(val)) 39 | } 40 | 41 | /// Take the value out of this `MoveCell`, leaving nothing in its place. 42 | pub fn take(&self) -> Option { 43 | unsafe { self.as_mut().take() } 44 | } 45 | 46 | /// Take the value out of this `MoveCell`, leaving a clone in its place. 47 | pub fn clone_inner(&self) -> Option where T: Clone { 48 | let inner = self.take(); 49 | inner.clone().map(|inner| self.put(inner)); 50 | inner 51 | } 52 | 53 | /// Check if this `MoveCell` contains a value or not. 54 | pub fn has_value(&self) -> bool { 55 | unsafe { self.as_ref().is_some() } 56 | } 57 | } 58 | 59 | impl Default for MoveCell { 60 | fn default() -> Self { 61 | MoveCell::new() 62 | } 63 | } 64 | 65 | -------------------------------------------------------------------------------- /src/widget.rs: -------------------------------------------------------------------------------- 1 | //! Operations common to all widget types. 2 | 3 | use utils::cstr::AsCStr; 4 | 5 | use base::{BaseWidget, Downcast}; 6 | use dialog::Dialog; 7 | use widget_prelude::IUPPtr; 8 | 9 | use ::KISSContext; 10 | 11 | use iup_sys; 12 | 13 | use std::borrow::Borrow; 14 | use std::cell::Cell; 15 | use std::ffi::{CStr, CString}; 16 | use std::fmt::{self, Debug, Display, Formatter}; 17 | use std::hash::{Hash, Hasher}; 18 | use std::ops::Deref; 19 | use std::ptr; 20 | use std::rc::Rc; 21 | 22 | /// Trait implemented for all widget types. 23 | /// 24 | /// Some methods may not apply to some widgets. 25 | pub trait Widget: IUPWidget { 26 | /// Show this widget if it was previously hidden. 27 | /// 28 | /// Does nothing if the widget is already shown, or if the operation does not apply. 29 | fn show(self) -> Self { 30 | unsafe { iup_sys::IupShow(self.ptr()); } 31 | self 32 | } 33 | 34 | /// Hide this widget if it was previously visible. 35 | /// 36 | /// Does nothing if the widget is already hidden, or if the operation does not apply. 37 | fn hide(self) -> Self { 38 | unsafe { iup_sys::IupHide(self.ptr()); } 39 | self 40 | } 41 | 42 | /// Set the widget's visibility state. 43 | /// 44 | /// `.set_visible(true)` is equivalent to calling `.show()`, and `.set_visible(false)` 45 | /// is equivalent to calling `.hide()`. 46 | /// 47 | /// Does nothing if the widget is in the same visibility state as the one being set, 48 | /// or if the operation does not apply. 49 | fn set_visible(self, visible: bool) -> Self { 50 | self.set_bool_attribute(::attrs::VISIBLE, visible); 51 | self 52 | } 53 | 54 | /// Set the widget's enabled state. 55 | /// 56 | /// When a widget is disabled, it does not react to user interaction or invoke any callbacks. 57 | /// 58 | /// Does nothing if the widget does not support being disabled. 59 | fn set_enabled(self, enabled: bool) -> Self { 60 | self.set_bool_attribute(::attrs::ACTIVE, enabled); 61 | self 62 | } 63 | 64 | /// Set the position of this widget relative to the top-left corner of its parent. 65 | /// 66 | /// Does nothing if the widget is not renderable or not attached to a parent. 67 | fn set_position(self, x: i32, y: i32) -> Self { 68 | self.set_str_attribute(::attrs::POSITION, format!("{x},{y}", x=x, y=y)); 69 | self 70 | } 71 | 72 | /// Get the position of this widget relative to the top-left corner of its parent. 73 | /// 74 | /// Returns (0, 0) if the widget is not renderable, not attached to a parent, or if that is the 75 | /// widget's actual relative position. 76 | fn get_position(self) -> (i32, i32) { 77 | self.get_int2_attribute(::attrs::POSITION) 78 | } 79 | 80 | /// Set the name of the widget so it can be found within its parent. 81 | /// 82 | /// Does nothing if the widget does not support having a name. 83 | /// 84 | /// ##Panics 85 | /// If any `WidgetStr` instances from `self.get_name()` are still reachable. 86 | fn set_name(self, name: &str) -> Self { 87 | self.set_str_attribute(::attrs::NAME, name); 88 | self 89 | } 90 | 91 | /// Get the name of this widget, if the widget supports having a name and one is set. 92 | fn get_name(&self) -> Option { 93 | self.get_str_attribute(::attrs::NAME) 94 | } 95 | 96 | /// Get the next child in the parent after this widget, based on the order in which they were 97 | /// added. 98 | /// 99 | /// Returns `None` if this widget is an only child or is not attached to a parent. 100 | fn get_sibling(self) -> Option { 101 | unsafe { 102 | let ptr = iup_sys::IupGetBrother(self.ptr()); 103 | BaseWidget::from_ptr_opt(ptr) 104 | } 105 | } 106 | 107 | /// Get the parent of this widget. 108 | /// 109 | /// Returns `None` if this widget has no parent. 110 | fn get_parent(self) -> Option { 111 | unsafe { 112 | let ptr = iup_sys::IupGetParent(self.ptr()); 113 | BaseWidget::from_ptr_opt(ptr) 114 | } 115 | } 116 | 117 | /// Get the containing dialog of this widget. 118 | /// 119 | /// Returns `None` if this widget is not attached to a dialog. 120 | fn get_dialog(self) -> Option { 121 | unsafe { 122 | let ptr = iup_sys::IupGetDialog(self.ptr()); 123 | // Note to self: not using UFCS because `downcast()` is an unsafe function. 124 | BaseWidget::from_ptr_opt(ptr).map(|base| Dialog::downcast(base)) 125 | } 126 | } 127 | 128 | /// Get the rendered size of this widget, in pixels. 129 | /// 130 | /// Returns `(0, 0)` if this widget has no rendered size. 131 | fn get_size_pixels(self) -> (u32, u32) { 132 | let (width, height) = self.get_int2_attribute(::attrs::RASTERSIZE); 133 | (width as u32, height as u32) 134 | } 135 | 136 | /// Store this widget under `name`, returning the previous widget stored, if any. 137 | /// 138 | /// It may later be retrieved from any valid KISS-UI context 139 | /// by calling `BaseWidget::load(name)`. 140 | fn store>(self, name: N) -> Option { 141 | KISSContext::store_widget(name, self) 142 | } 143 | 144 | fn to_base(self) -> BaseWidget { 145 | unsafe { BaseWidget::from_ptr(self.ptr()) } 146 | } 147 | } 148 | 149 | pub trait Destroy: Widget { 150 | fn destroy(self) { 151 | unsafe { 152 | iup_sys::IupDestroy(self.ptr()); 153 | } 154 | } 155 | } 156 | 157 | /// A string slice borrowed from a widget's metadata. Can be dereferenced to `&str`. 158 | /// 159 | /// Tracks ownership of the string so that its pointer cannot be invalidated. Releases ownership 160 | /// on-drop. 161 | pub struct WidgetStr<'a> { 162 | refcount: Rc>, 163 | data: &'a str, 164 | } 165 | 166 | impl<'a> WidgetStr<'a> { 167 | pub fn new(ptr: *mut iup_sys::Ihandle, name: &'static str, str_data: &'a str) -> WidgetStr<'a> { 168 | let widgetStr = WidgetStr { 169 | refcount: KISSContext::str_refcount(ptr, name), 170 | data: str_data, 171 | }; 172 | widgetStr.inc_refcount(); 173 | widgetStr 174 | } 175 | 176 | fn inc_refcount(&self) { 177 | let refcount = self.refcount.get(); 178 | self.refcount.set(refcount + 1); 179 | } 180 | } 181 | 182 | impl<'a> Borrow for WidgetStr<'a> { 183 | fn borrow(&self) -> &str { 184 | self.data 185 | } 186 | } 187 | 188 | impl<'a> Debug for WidgetStr<'a> { 189 | fn fmt(&self, fmt: &mut Formatter) -> Result<(), fmt::Error> { 190 | ::fmt(self.data, fmt) 191 | } 192 | } 193 | 194 | impl<'a> Display for WidgetStr<'a> { 195 | fn fmt(&self, fmt: &mut Formatter) -> Result<(), fmt::Error> { 196 | ::fmt(self.data, fmt) 197 | } 198 | } 199 | 200 | impl<'a> Hash for WidgetStr<'a> { 201 | fn hash(&self, hasher: &mut H) where H: Hasher { 202 | self.data.hash(hasher) 203 | } 204 | } 205 | 206 | impl<'a> Deref for WidgetStr<'a> { 207 | type Target = str; 208 | 209 | fn deref<'b>(&'b self) -> &'b str { 210 | self.data 211 | } 212 | } 213 | 214 | impl<'a> Clone for WidgetStr<'a> { 215 | fn clone(&self) -> Self { 216 | self.inc_refcount(); 217 | 218 | WidgetStr { 219 | refcount: self.refcount.clone(), 220 | data: self.data, 221 | } 222 | } 223 | } 224 | 225 | impl<'a> Drop for WidgetStr<'a> { 226 | fn drop(&mut self) { 227 | let refcount = self.refcount.get(); 228 | self.refcount.set(refcount - 1); 229 | } 230 | } 231 | 232 | 233 | 234 | #[doc(hidden)] 235 | pub trait IUPWidget: Copy { 236 | unsafe fn from_ptr(ptr: IUPPtr) -> Self; 237 | 238 | unsafe fn from_ptr_opt(ptr: IUPPtr) -> Option { 239 | if !ptr.is_null() { 240 | Some(Self::from_ptr(ptr)) 241 | } else { 242 | None 243 | } 244 | } 245 | 246 | fn ptr(self) -> IUPPtr; 247 | 248 | fn classname(&self) -> &CStr { 249 | unsafe { CStr::from_ptr(iup_sys::IupGetClassName(self.ptr())) } 250 | } 251 | 252 | fn set_str_attribute(self, name: &'static str, val: V) where V: Into { 253 | KISSContext::assert_str_not_borrowed(self.ptr(), name); 254 | 255 | let c_val = CString::new(val.into()).unwrap(); 256 | unsafe { iup_sys::IupSetStrAttribute(self.ptr(), name.as_cstr(), c_val.as_ptr()); } 257 | } 258 | 259 | fn set_opt_str_attribute(self, name: &'static str, val: Option) where V: Into { 260 | KISSContext::assert_str_not_borrowed(self.ptr(), name); 261 | 262 | let c_val = val.map(V::into).map(CString::new).map(Result::unwrap); 263 | unsafe { 264 | iup_sys::IupSetStrAttribute( 265 | self.ptr(), 266 | name.as_cstr(), 267 | // This looks backwards, but check the docs. It's right. 268 | c_val.as_ref().map_or_else(ptr::null, |c_val| c_val.as_ptr()) 269 | ) 270 | } 271 | } 272 | 273 | fn set_const_str_attribute(self, name: &'static str, val: &'static str) { 274 | KISSContext::assert_str_not_borrowed(self.ptr(), name); 275 | 276 | unsafe { iup_sys::IupSetAttribute(self.ptr(), name.as_cstr(), val.as_cstr()); } 277 | } 278 | 279 | fn get_str_attribute(&self, name: &'static str) -> Option { 280 | let ptr = unsafe { iup_sys::IupGetAttribute(self.ptr(), name.as_cstr()) }; 281 | 282 | if !ptr.is_null() { 283 | unsafe { 284 | // Safe since we're controlling the lifetime 285 | let c_str = CStr::from_ptr(ptr); 286 | // We're forcing IUP to use UTF-8 287 | let str_data = ::std::str::from_utf8_unchecked(c_str.to_bytes()); 288 | 289 | Some(WidgetStr::new(self.ptr(), name, str_data)) 290 | } 291 | } else { 292 | None 293 | } 294 | } 295 | 296 | fn set_int_attribute(self, name: &'static str, val: i32) { 297 | unsafe { iup_sys::IupSetInt(self.ptr(), name.as_cstr(), val); } 298 | } 299 | 300 | fn get_int_attribute(self, name: &'static str) -> i32 { 301 | unsafe { iup_sys::IupGetInt(self.ptr(), name.as_cstr()) } 302 | } 303 | 304 | fn get_int2_attribute(self, name: &'static str) -> (i32, i32) { 305 | let mut left = 0; 306 | let mut right = 0; 307 | 308 | unsafe { 309 | assert!(iup_sys::IupGetIntInt(self.ptr(), name.as_cstr(), &mut left, &mut right) != 0); 310 | } 311 | 312 | (left, right) 313 | } 314 | 315 | fn set_float_attribute(self, name: &'static str, val: f32) { 316 | unsafe { iup_sys::IupSetFloat(self.ptr(), name.as_cstr(), val); } 317 | } 318 | 319 | fn get_float_attribute(self, name: &'static str) -> f32 { 320 | unsafe { iup_sys::IupGetFloat(self.ptr(), name.as_cstr()) } 321 | } 322 | 323 | fn set_bool_attribute(self, name: &'static str, val: bool) { 324 | let val = ::attrs::values::bool_yes_no(val); 325 | self.set_const_str_attribute(name, val); 326 | } 327 | 328 | fn set_attr_handle(self, name: &'static str, handle: W) { 329 | unsafe { iup_sys::IupSetAttributeHandle(self.ptr(), name.as_cstr(), handle.ptr()); } 330 | } 331 | 332 | fn get_attr_handle(self, name: &'static str) -> Option { 333 | unsafe { 334 | let existing = iup_sys::IupGetAttributeHandle(self.ptr(), name.as_cstr()); 335 | BaseWidget::from_ptr_opt(existing) 336 | } 337 | } 338 | 339 | fn set_callback(self, name: &'static str, callback: ::iup_sys::Icallback) { 340 | unsafe { iup_sys::IupSetCallback(self.ptr(), name.as_cstr(), callback); } 341 | } 342 | } 343 | 344 | impl<'a, T: IUPWidget> IUPWidget for &'a T { 345 | unsafe fn from_ptr(_ptr: *mut iup_sys::Ihandle) -> Self { 346 | panic!("Cannot construct an &mut Self from a pointer"); 347 | } 348 | 349 | fn ptr(self) -> *mut iup_sys::Ihandle { 350 | (*self).ptr() 351 | } 352 | } 353 | 354 | macro_rules! impl_widget { 355 | ($ty:ident, [$($classname:expr),+]) => { 356 | impl_widget!($ty); 357 | 358 | impl ::base::Downcast for $ty { 359 | fn can_downcast(base: &::base::BaseWidget) -> bool { 360 | [$($classname.as_bytes()),+].contains(&base.classname().to_bytes()) 361 | } 362 | } 363 | }; 364 | 365 | ($ty:ident, $classname:expr) => { 366 | impl_widget!($ty); 367 | 368 | impl ::base::Downcast for $ty { 369 | fn can_downcast(base: &::base::BaseWidget) -> bool { 370 | $classname.as_bytes() == base.classname().to_bytes() 371 | } 372 | } 373 | }; 374 | 375 | ($ty:ident) => { 376 | impl ::widget::IUPWidget for $ty { 377 | unsafe fn from_ptr(ptr: ::widget_prelude::IUPPtr) -> Self { 378 | assert!( 379 | !ptr.is_null(), 380 | concat!( 381 | concat!("Failed to construct ", stringify!($ty)), 382 | "; pointer returned from IUP was null!" 383 | ) 384 | ); 385 | 386 | $ty(ptr) 387 | } 388 | 389 | fn ptr(self) -> ::widget_prelude::IUPPtr { 390 | self.0 391 | } 392 | } 393 | 394 | impl ::widget::Widget for $ty {} 395 | 396 | impl Copy for $ty {} 397 | 398 | impl Clone for $ty { 399 | fn clone(&self) -> Self { 400 | *self 401 | } 402 | } 403 | } 404 | } 405 | --------------------------------------------------------------------------------