Repository: cybergeek94/kiss-ui Branch: master Commit: 074bf82df1b9 Files: 28 Total size: 71.7 KB Directory structure: gitextract_5c7epxqh/ ├── .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 └── 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 ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ target Cargo.lock *~ *.swp ================================================ FILE: .travis.yml ================================================ env: global: - IUP_DL='http://sourceforge.net/projects/iup/files/3.14/Linux%20Libraries/iup-3.14_Linux32_64_lib.tar.gz' language: rust install: - sudo apt-get install libgtk-3-dev # Download and install IUP - mkdir iup_libs/ - wget $IUP_DL -O iup_libs.tar.gz - tar -xzvf iup_libs.tar.gz -C iup_libs/ # By piping a newline to ./install, we skip the enter prompt - (cd iup_libs/ && echo -ne '\n' | sudo ./install) - rm -rf iup_libs/ script: - cargo build -v - cargo test -v - cargo doc -v --no-deps ================================================ FILE: Cargo.toml ================================================ [package] name = "kiss-ui" version = "0.1.0" authors = ["Austin Bonander "] [dependencies] libc = "*" iup-sys = "*" ================================================ FILE: LICENSE ================================================ The MIT License (MIT) Copyright (c) 2015 Austin Bonander Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ KISS-UI [![Build Status](https://travis-ci.org/cybergeek94/kiss-ui.svg?branch=master)](https://travis-ci.org/cybergeek94/kiss-ui) ========= A UI framework for Rust based on the KISS (Keep It Simple, Stupid!) philosophy. Powered by the [IUP][iup] GUI library for C by Tecgraf, via the bindings created for [iup-rust][iup-rust]. (No relation to the equally awesome [kiss3d][kiss3d].) [kiss3d]: https://github.com/sebcrozet/kiss3d [iup]: http://webserver2.tecgraf.puc-rio.br/iup/ [iup-rust]: https://github.com/dcampbell24/iup-rust Contents -------- * [Documentation](#documentation) * [Usage](#usage) * [Installing IUP Binaries](#installing-iup-binaries) * [Windows](#windows) * [Linux](#linux) * [OS X](#os-x) * [Comparison to Other UI Frameworks](#comparison-to-other-ui-frameworks) * [Enabling Visual Styles on Windows](#enabling-visual-styles-on-windows) Documentation ------------- [`kiss-ui` docs hosted on Github Pages](http://kiss-ui.github.io/kiss-ui/doc/kiss_ui/) Usage ----- Simply add the following to your `Cargo.toml`: ``` [dependencies.kiss-ui] git = "https://github.com/cybergeek94/kiss-ui" ``` Import KISS-UI's macros and common types: ```rust #[macro_use] extern crate kiss_ui; use kiss_ui::prelude::*; ``` #### KISS-UI builds on all Rust release channels! [iup-dl]: http://sourceforge.net/projects/iup/files/3.14/ Installing IUP Binaries ------------------- You will need to install the IUP binaries for your system, which are available for download [here][iup-dl]. 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. PRs amending or adding instructions for any platform are very welcome. *** ### Windows #### Dynamic linking 1. Navigate to `Windows Libraries/Dynamic` * 32-bit: Download `iup-3.14_Win32_dllw4_lib.zip` * 64-bit: Download `iup-3.14_Win64_dllw4_lib.zip` 2. Extract all `.dll` files to a folder where the linker can find them (pick one): * `/bin/rustlib//lib/` (recommended) * (using MinGW/MSYS) `/usr/lib` * `/bin/` 3. Copy the same DLLs to a folder in your PATH (pick one): * `/bin/` (recommended) * Create a folder anywhere and add it to your PATH. * Add one of the folders from step 2 to your PATH. You should **NEVER** place arbitrary files in your Windows install folder, no matter how benign. #### Static Linking 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. *** ### Linux 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. #### Compile from Source 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. #### Download the Precompiled Binaries However, if you would rather download the precompiled binaries, begin by going to [the download page][iup-dl]. 1. Navigate to the `Linux Libraries` folder. 2. Identify your kernel version. This can be done by entering the command `uname -r` into a terminal. * If you don't know if your Linux is 32-bit or 64-bit, use the command `uname -a` and look for the following: * `x86_64`: Your system is 64-bit. * `x86`: Your system is 32-bit. 3. Select and download the tarball for your kernel version and bit-arity. * For 32-bit (`x86`), there is only one package: `iup-3.14_Linux32_lib.tar.gz` * For 64-bit (`x86_64`), select one of the following based on your kernel version: * **>= 3.19**: `iup-3.14_Linux319_64_lib.tar.gz` * **>= 3.13**: `iup-3.14_Linux313_64_lib.tar.gz` * **>= 3.5**: `iup-3.14_Linux35_64_lib.tar.gz` * **>= 3.2**: `iup-3.14_Linux32_64_lib.tar.gz` * **2.6**: `iup-3.14_Linux26g4_64_lib.tar.gz` 4. Navigate to the folder where you downloaded the tarball to in a terminal. 5. Extract the tarball: * `mkdir iup_libs/` * `tar -xzvf -C iup_libs/` 6. Install the binaries: * `cd iup_libs/` (The install script must be run in its folder.) * You can run either, or both, of the following two commands: * To install the dynamic libraries: `sudo ./install` * To install the static libraries: `sudo ./install_dev` 7. Follow the prompts in the installer. 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. [iup-compile]: http://webserver2.tecgraf.puc-rio.br/iup/en/guide.html#buildlib [iup-source]: http://webserver2.tecgraf.puc-rio.br/iup/en/svn.html *** ### OS X 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. To install GTK+ 2: ``` brew install gtk+ ``` To install GTK+ 3: ``` brew install gtk+3 ``` **Note:** if you have troubles building after installing GTK+ 3, please consult [this StackOverflow answer](http://stackoverflow.com/a/20114598/1299804). 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. Once you have downloaded the tarball, the installation process *should be* equivalent to Linux's starting at **Step 4**. [os-x]: http://sourceforge.net/projects/iup/files/3.14/Other%20Libraries/ *** Comparison to Other UI Frameworks --------------------------------- **NOTE**: This list is *far* from exhaustive and may contain outdated information. Pull requests for corrections and additions are welcome! * KISS-UI * Build Status: [![Build Status](https://travis-ci.org/cybergeek94/kiss-ui.svg?branch=master)](https://travis-ci.org/cybergeek94/kiss-ui) * Supported Platforms: Windows (using Win32 APIs), Linux and Mac (using GTK+) * Native Look and Feel: **Yes** * "Hello, World!" LOC: **[18][kiss-ui-hw]** * External Crates: **2** * External Native Libs: 1 * [PistonDevelopers/conrod][conrod] * Build Status: [![Build Status](https://travis-ci.org/PistonDevelopers/conrod.svg?branch=master)](https://travis-ci.org/PistonDevelopers/conrod) * Supported Platforms: Windows, Mac, Linux * Native Look and Feel: No * "Hello, World!" LOC: [40][conrod-hw] (estimated based on linked example) * External Crates: 9 (not including testing crates and transitive dependencies) * External Native Libs: **~0** (depends on backend used) * [rust-gnome/gtk][rgtk] * Build Status: [![Build Status](https://travis-ci.org/rust-gnome/gtk.png?branch=master)](https://travis-ci.org/rust-gnome/gtk) * Supported Platforms: Windows, Mac, Linux * Native Look and Feel: **Yes** * "Hello, World!" LOC: [23][rust-gnome-hw] * External Crates: 10 (1 local but pulled from Crates.io) * External Native Libs: ~5 (installed on most Linux distros/external on Windows, Mac) 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. Enabling Visual Styles on Windows --------------------------------- 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. 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. 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. [kiss-ui-hw]: https://github.com/cybergeek94/kiss-ui/blob/master/examples/window_test.rs [conrod]: https://github.com/PistonDevelopers/conrod [conrod-hw]: https://github.com/PistonDevelopers/conrod/blob/master/examples/counter.rs [rust-gnome-hw]: https://github.com/rust-gnome/examples/blob/master/src/basic.rs [rgtk]: https://github.com/rust-gnome/gtk ================================================ FILE: SCREENSHOTS.md ================================================ KISS-UI Screenshots ------------------- 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. We at KISS-UI aren't exactly designers, so feel free to let us know how we can make these better aesthetically. [nightlife-mint]: http://cinnamon-spices.linuxmint.com/themes/view/31 ###`examples/button_test.rs` This shows the main example as well as the dialogs that pop up when the "Message" and "Alert" buttons are clicked, respectively. ####Windows Not sure why the borders are cut off on the other dialogs. ![](screenshots/Windows/button_test.png) ![](screenshots/Windows/button_test_message.png) ![](screenshots/Windows/button_test_alert.png) ####GTK+ ![](screenshots/GTK+/button_test.png) ![](screenshots/GTK+/button_test_message.png) ![](screenshots/GTK+/button_test_alert.png) ###`examples/progress_test.rs` **Note**: the dashed version of the progress bar isn't always distinguished. It depends on the platform and visual theme being used. ####Windows ![](screenshots/Windows/progress_test.png) ####GTK+ ![](screenshots/GTK+/progress_test.png) ###`examples/show_image.rs` [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) ####Windows ![](screenshots/Windows/show_image.png) ####GTK+ ![](screenshots/GTK+/show_image.png) ###`examples/textbox_test.rs` This example shows the main dialog as well as the one that pops up when the "Save" button is clicked. ####Windows 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. ![](screenshots/Windows/textbox_test.png) ####GTK+ The same was done in Linux. ![](screenshots/GTK+/textbox_test.png) ###`examples/window_test.rs` #####Note: resized to fit the page better. ####Windows ![](screenshots/Windows/window_test.png) ####GTK+ ![](screenshots/GTK+/window_test.png) ================================================ FILE: build_docs.sh ================================================ git merge master cargo doc rm -rf doc/ cp -avr target/doc/ doc/ ================================================ FILE: examples/button_test.rs ================================================ #[macro_use] extern crate kiss_ui; use kiss_ui::prelude::*; use kiss_ui::button::Button; use kiss_ui::container::{Horizontal, Vertical}; use kiss_ui::dialog::{self, AlertPopupBuilder}; fn main() { kiss_ui::show_gui(|| Dialog::new( Vertical::new(children![ Horizontal::new( children![ Button::new() .set_label("Message #1") .set_name("Message_1") .set_onclick(show_message_dialog), Button::new() .set_label("Message #2") .set_name("Message_2") .set_onclick(show_message_dialog), Button::new() .set_label("change the name") .set_name("bar") .set_onclick(show_change_name), ] ) .set_elem_spacing_pixels(10), Horizontal::new( children![ Button::new() .set_label("Alert") .set_onclick(show_alert_dialog), Button::new() .set_label("Close") .set_onclick(close_dialog), ] ) .set_elem_spacing_pixels(10) ]) ) .set_title("Button test!") ) } fn show_message_dialog(btn: Button) { let name = btn.get_name().unwrap(); dialog::message_popup("Good job!", format!("You clicked the button {:?}!", name)); } fn show_change_name(btn: Button) { let name = btn.get_name().unwrap(); dialog::message_popup("Lets try it!", format!("The button's name is '{}'!\n Now we try to change it!\n You will get a Panic if you still use the 'name'", name)); // drop(name); // <= uncomment this line btn.set_name("foo"); show_message_dialog(btn); } fn show_alert_dialog(_: Button) { let res = AlertPopupBuilder::new("Alert!", "You clicked the other button!", "Yes") .button2("No") .button3("Cancel") .popup(); println!("Alert result = {}", res); } fn close_dialog(_: Button) -> CallbackStatus { println!("Closing dialog!"); CallbackStatus::Close } ================================================ FILE: examples/progress_test.rs ================================================ #[macro_use] extern crate kiss_ui; use kiss_ui::callback::OnShow; use kiss_ui::container::Vertical; use kiss_ui::dialog::Dialog; use kiss_ui::progress::ProgressBar; use kiss_ui::text::Label; use kiss_ui::timer::Timer; fn main() { kiss_ui::show_gui(|| { let regular = ProgressBar::new(); let dashed = ProgressBar::new().set_dashed(true); let dialog = Dialog::new( Vertical::new( children![ Label::new("Regular:"), regular.clone(), Label::new("Dashed:"), dashed.clone(), Label::new("Indefinite:"), ProgressBar::new().set_indefinite(true), ] ) ); dialog .set_title("Progressbar Test") .set_on_show(move |_| { let on_timer_interval = move |timer: Timer|{ regular.add_value(0.1); dashed.add_value(0.1); if regular.get_value() == 1.0 { timer.stop(); } }; Timer::new() .set_interval(1000) .set_on_interval(on_timer_interval) .start(); }); dialog }); } ================================================ FILE: examples/show_image.rs ================================================ #[macro_use] extern crate kiss_ui; use kiss_ui::container::Horizontal; use kiss_ui::dialog::Dialog; use kiss_ui::image::{Image, ImageContainer}; use kiss_ui::text::Label; fn main() { const WIDTH: u32 = 512; const HEIGHT: u32 = 512; let image_data: Vec<_> = (0..HEIGHT) .flat_map(|y| (0..WIDTH).map(move |x| color(x, y))) .collect(); kiss_ui::show_gui(|| { Dialog::new( Horizontal::new( children![ Label::new("") .set_image(Image::new_rgb(WIDTH, HEIGHT, &image_data)), ] ) ) .set_title(format!("Image! ({width} x {height})", width=WIDTH, height=HEIGHT)) }); } /// Play with this function and let us know what you come up with! fn color(x: u32, y: u32) -> (u8, u8, u8) { // Suggested by /u/GBGamer117 ((x ^ y) as u8, y as u8, x as u8) // Suggested by /u/Effnote // ((x ^ y) as u8, ((x + 2) ^ (y + 1)) as u8, ((x + 4) ^ (y + 2)) as u8) // Suggested by /u/ImSoCabbage // let val = (x ^ y) as u8; // (val, val, val) // (255 - val, val, val) // Inverted red } ================================================ FILE: examples/textbox_test.rs ================================================ #[macro_use] extern crate kiss_ui; use kiss_ui::prelude::*; use kiss_ui::button::Button; use kiss_ui::container::Vertical; use kiss_ui::dialog; use kiss_ui::text::{Label, TextBox}; fn main() { kiss_ui::show_gui(|| { Dialog::new( Vertical::new( children![ Label::new("Enter a message:"), TextBox::new() .set_visible_columns(20) .set_name("my_textbox"), Button::new() .set_label("Save") .set_onclick(show_alert_message), ] ) ) .set_title("Textbox Test") }); } fn show_alert_message(clicked: Button) { let dialog = clicked.get_dialog().unwrap(); let text_box = dialog.get_child("my_textbox").unwrap() .try_downcast::().ok().expect("child my_textbox was not a TextBox!"); let text = text_box.get_text(); dialog::message_popup("Message saved!", format!("Your message: {}", text)); } ================================================ FILE: examples/window_test.rs ================================================ #[macro_use] extern crate kiss_ui; use kiss_ui::container::Horizontal; use kiss_ui::dialog::Dialog; use kiss_ui::text::Label; fn main() { kiss_ui::show_gui(|| { Dialog::new( Horizontal::new( children![ Label::new("Hello, world!"), ] ) ) .set_title("Hello, world!") .set_size_pixels(640, 480) }); } ================================================ FILE: kiss-app.manifest ================================================ KISS-UI Application ================================================ FILE: src/attrs.rs ================================================ c_str_consts! { //Globals UTF8_MODE = "UTF8MODE", // Basic widget attributes TITLE = "TITLE", VALUE = "VALUE", ACTIVE = "ACTIVE", NAME = "NAME", VISIBLE = "VISIBLE", // Rendering attributes RASTERSIZE = "RASTERSIZE", POSITION = "POSITION", // Layout attributes ALIGNMENT_VERT = "ALIGNMENTLIN", ALIGNMENT_HORI = "ALIGNMENTCOL", ORIENTATION = "ORIENTATION", NUMDIV = "numdiv", // Specific to `Absolute` CX = "CX", CY = "CY", //Textbox attributes MULTILINE = "MULTILINE", VISIBLE_COLUMNS = "VISIBLECOLUMNS", VISIBLE_LINES = "VISIBLELINES", // Progressbar attributes DASHED = "DASHED", MARQUEE = "MARQUEE", MIN = "MIN", MAX = "MAX", //Timer attribute TIME = "TIME", RUN = "RUN", // Spacing between elements in a container GAP = "GAP", // Handles IMAGE = "IMAGE", //Callbacks ACTION = "ACTION", ACTION_CB = "ACTION_CB", VALUE_CHANGED_CB = "VALUECHANGED_CB", MAP_CB = "MAP_CB", } pub mod values { c_str_consts! { YES = "YES", NO = "NO", } pub fn bool_yes_no(_bool: bool) -> &'static str { match _bool { true => YES, false => NO, } } } ================================================ FILE: src/base.rs ================================================ //! A general widget type that can be specialized at runtime. use widget_prelude::*; use ::KISSContext; use std::borrow::Borrow; /// A general widget type that can be specialized at runtime via `Downcast`. pub struct BaseWidget(IUPPtr); impl BaseWidget { /// Attempt to load a widget named by `name` from internal storage. /// /// If successful, the `BaseWidget` can then be downcast to the original widget type. /// /// Returns `None` if no widget by that name was found. /// /// ##Panics /// If called before `kiss_ui::show_gui()` is invoked or after it returns. pub fn load>(name: N) -> Option { KISSContext::load_widget(&name) } /// Attempt to downcast this `BaseWidget` to a more specialized widget type. /// /// This will return an error if the underlying widget class is different than the one /// it is being cast to. pub fn try_downcast(self) -> Result where T: Downcast { T::try_downcast(self) } } impl_widget! { BaseWidget } /// A trait describing a widget's ability to be downcast from `BaseWidget`. pub trait Downcast: Widget { /// Attempt to downcast `base` to the `Self` type, /// returning `Err(base)` if unsuccessful. fn try_downcast(base: BaseWidget) -> Result { if Self::can_downcast(&base) { Ok(unsafe { Self::downcast(base) }) } else { Err(base) } } // These are not meant for end-users to call. // They are an implementation detail of `try_downcast()`. #[doc(hidden)] unsafe fn downcast(base: BaseWidget) -> Self { Self::from_ptr(base.ptr()) } #[doc(hidden)] fn can_downcast(base: &BaseWidget) -> bool; } ================================================ FILE: src/button.rs ================================================ //! Buttons that can receive user input. use widget_prelude::*; use std::ptr; /// A button that can be clicked momentarily and invoke a callback when this happens. pub struct Button(IUPPtr); impl Button { /// Create a new `Button` with no label. pub fn new() -> Button { unsafe { let ptr = ::iup_sys::IupButton(ptr::null(), ptr::null()); Self::from_ptr(ptr) } } /// Set the label of this button. Can be blank. pub fn set_label>(self, label: L) -> Self { self.set_str_attribute(::attrs::TITLE, label); self } } impl_widget! { Button, "button" } impl_onclick! { Button } impl ::image::ImageContainer for Button {} ================================================ FILE: src/callback.rs ================================================ //! Traits for notifying client code when the state of a KISS-UI widget is updated. use widget_prelude::*; use iup_sys::Ihandle; use std::cell::RefCell; use std::collections::HashMap; /// Set this within a callback to tell the framework if it should close or not. /// /// If `Callback::close.set()` is called within a callback, then when the callback returns, /// the dialog containing the widget on which the callback was invoked will be closed. #[derive(Copy, Clone, PartialEq, Eq)] pub enum CallbackStatus { //Ignore, /// The default `CallbackStatus`, does nothing when set. Default, /// If this is set within a callback, then when the callback returns the dialog containing the /// widget on which the callback was invoked will be closed. Close, //Continue, } impl CallbackStatus { pub fn close(&mut self) { *self = CallbackStatus::Close; } #[doc(hidden)] pub fn to_cb_return(self) -> ::libc::c_int { use self::CallbackStatus::*; match self { Close => ::iup_sys::IUP_CLOSE, Default => ::iup_sys::IUP_DEFAULT, // _ => unimplemented!(), } } } impl From<()> for CallbackStatus { fn from(_: ()) -> CallbackStatus { CallbackStatus::Default } } pub trait Callback: 'static { fn on_callback(&mut self, args: Args) -> CallbackStatus; } impl, F: 'static> Callback for F where F: FnMut(Args) -> Out { /// Because of the `impl From<()> for CallbackStatus`, closures that return `()` can be /// accepted by this impl. fn on_callback(&mut self, args: Args) -> CallbackStatus { self(args).into() } } #[doc(hidden)] pub type CallbackMap = RefCell>>>; macro_rules! callback_impl { ($cb_attr:expr, $base:expr, $callback:expr, $self_ty:ident) => ( { thread_local!( static CALLBACKS: ::callback::CallbackMap<$self_ty> = ::std::cell::RefCell::new(::std::collections::HashMap::new()) ); extern fn extern_callback(element: *mut ::iup_sys::Ihandle) -> ::libc::c_int { use ::callback::CallbackStatus; let widget = unsafe { $self_ty::from_ptr(element) }; CALLBACKS.with(|callbacks| callbacks.borrow_mut() .get_mut(&widget.ptr()) .map(|cb| cb.on_callback(widget)) ).unwrap_or(CallbackStatus::Default).to_cb_return() } CALLBACKS.with(|callbacks| callbacks.borrow_mut().insert($base.ptr(), Box::new($callback)) ); $base.set_callback($cb_attr, extern_callback); } ) } /// A trait describing a widget that can be clicked, and can notify client code when this occurs. pub trait OnClick: Widget { fn set_onclick(self, on_click: Cb) -> Self where Cb: Callback; } macro_rules! impl_onclick { ($self_ty:ident) => ( impl $crate::callback::OnClick for $self_ty { fn set_onclick(self, on_click: Cb) -> Self where Cb: ::callback::Callback { callback_impl! { $crate::attrs::ACTION, self, on_click, $self_ty } self } } ) } /// A trait describing a widget which has a value that can be changed by the user, and can notify /// client code when this occurs. pub trait OnValueChange: Widget { fn set_on_value_changed(self, on_value_chaged: Cb) -> Self where Cb: Callback; } macro_rules! impl_on_value_change { ($self_ty:ident) => ( impl $crate::callback::OnValueChange for $self_ty { fn set_on_value_changed(self, on_value_changed: Cb) -> Self where Cb: ::callback::Callback { callback_impl! { $crate::attrs::VALUE_CHANGED_CB, self, on_value_changed, $self_ty } self } } ) } /// A trait describing a widget that can be shown, and can notify client code when this occurs. pub trait OnShow: Widget { fn set_on_show(self, on_show: Cb) -> Self where Cb: Callback; } macro_rules! impl_on_show { ($self_ty:ident) => ( impl ::callback::OnShow for $self_ty { fn set_on_show(self, on_show: Cb) -> Self where Cb: ::callback::Callback { callback_impl! { ::attrs::MAP_CB, self, on_show, $self_ty } self } } ) } ================================================ FILE: src/container.rs ================================================ //! Assorted types that can contain multiple widgets. //! //! All container types can be nested. //! //! Use the `children!{}` macro in this crate to convert a heterogeneous list of widgets into a //! `Vec` for the container constructors. use base::BaseWidget; use widget_prelude::*; /// Vertical alignment setting, used by `Horizontal` and `Grid`. #[derive(Copy, Clone)] pub enum VAlign { Top, Center, Bottom, } impl VAlign { fn as_cstr(self) -> &'static str { use self::VAlign::*; match self { Top => cstr!("ATOP"), Center => cstr!("ACENTER"), Bottom => cstr!("ABOTTOM"), } } } /// Horizontal alignment setting, used by `Vertical` and `Grid`. #[derive(Copy, Clone)] pub enum HAlign { Left, Center, Right, } impl HAlign { fn as_cstr(self) -> &'static str { use self::HAlign::*; match self { Left => cstr!("ALEFT"), Center => cstr!("ACENTER"), Right => cstr!("ARIGHT"), } } } /// The behavior of this enum depends on its point of use. #[derive(PartialEq, Eq, Copy, Clone)] pub enum Orientation { Vertical, Horizontal, } impl Orientation { #[doc(hidden)] pub fn as_cstr(self) -> &'static str { use self::Orientation::*; match self { Vertical => cstr!("VERTICAL"), Horizontal => cstr!("HORIZONTAL"), } } } fn raw_handle_vec(widgets: B) -> Vec where B: AsRef<[BaseWidget]> { let mut raw_handles: Vec<_> = widgets.as_ref().iter().cloned().map(BaseWidget::ptr).collect(); raw_handles.push(::std::ptr::null_mut()); raw_handles } /// A builder for `Absolute`, used to create and add children. pub struct AbsoluteBuilder { handles: Vec, } impl AbsoluteBuilder { fn new() -> AbsoluteBuilder { AbsoluteBuilder { handles: Vec::new() } } /// Add a child to the `Absolute` at the given coordinates, relative to the top-left corner of /// the container. pub fn add_child_at(&mut self, x: u32, y: u32, child: W) -> &mut Self { Absolute::set_child_pos(x, y, child); self.handles.push(child.ptr()); self } } /// A container type that makes no effort to arrange its children. Instead, they must be positioned /// manually. pub struct Absolute(IUPPtr); impl Absolute { /// Create a new absolute container using the given closure, which will be passed a mutable builder /// instance. /// /// The builder is necessary to ensure that the children have their positions set correctly, as /// `Widget::set_position` will not set the attributes that this particular container is /// expecting. pub fn new(build_fn: F) -> Absolute where F: FnOnce(&mut AbsoluteBuilder) { let mut builder = AbsoluteBuilder::new(); build_fn(&mut builder); unsafe { let ptr = ::iup_sys::IupCboxv(builder.handles.as_mut_ptr()); Self::from_ptr(ptr) } } /// Set the position of a child of an `Absolute` relative to its top-left corner. pub fn set_child_pos(x: u32, y: u32, child: W) { child.set_int_attribute(::attrs::CX, x as i32); child.set_int_attribute(::attrs::CY, y as i32); } } impl_widget! { Absolute, "cbox" } /// A container widget that lines up its children from left to right. pub struct Horizontal(IUPPtr); impl Horizontal { /// Create a new horizontal container with the given vector or array of children, which may /// also be empty. /// /// See the `children![]` macro in this crate for more info. pub fn new(children: C) -> Horizontal where C: AsRef<[BaseWidget]> { let mut raw_handles = raw_handle_vec(children); unsafe { let ptr = ::iup_sys::IupHboxv(raw_handles.as_mut_ptr()); Self::from_ptr(ptr) } } pub fn set_valign(self, valign: VAlign) -> Self { self.set_const_str_attribute(::attrs::ALIGNMENT_VERT, valign.as_cstr()); self } pub fn set_elem_spacing_pixels(self, spacing: u32) -> Self { self.set_str_attribute(::attrs::GAP, spacing.to_string()); self } } impl_widget! { Horizontal, "hbox" } /// A container widget that lines up its children from top to bottom. pub struct Vertical(IUPPtr); impl Vertical { pub fn new(children: C) -> Vertical where C: AsRef<[BaseWidget]> { let mut raw_handles = raw_handle_vec(children); unsafe { let ptr = ::iup_sys::IupVboxv(raw_handles.as_mut_ptr()); Self::from_ptr(ptr) } } pub fn set_halign(self, halign: HAlign) -> Self { self.set_const_str_attribute(::attrs::ALIGNMENT_HORI, halign.as_cstr()); self } pub fn set_elem_spacing_pixels(self, spacing: u32) -> Self { self.set_str_attribute(::attrs::GAP, spacing.to_string()); self } } impl_widget! { Vertical, "vbox" } /// A container widget that lines up its children from left to right, and from top to bottom. pub struct Grid(IUPPtr); impl Grid { pub fn new(children: C) -> Grid where C: AsRef<[BaseWidget]> { let mut raw_handles = raw_handle_vec(children); unsafe { let ptr = ::iup_sys::IupGridBoxv(raw_handles.as_mut_ptr()); Self::from_ptr(ptr) } } pub fn set_valign(self, valign: VAlign) -> Self { self.set_const_str_attribute(::attrs::ALIGNMENT_VERT, valign.as_cstr()); self } pub fn set_halign(self, halign: HAlign) -> Self { self.set_const_str_attribute(::attrs::ALIGNMENT_HORI, halign.as_cstr()); self } /// Based on the orientation, set the number of children to place in a: /// /// * `Vertical`: **column** /// * `Horizontal`: **row** /// /// before beginning the next one. pub fn set_ndiv(self, ndiv: u32) -> Self { self.set_int_attribute(::attrs::NUMDIV, ndiv as i32); self } /// Sets how children are distributed in the container. /// /// * `Vertical`: The container will fill columns first. /// /// Visual example (`ndiv=3` grid with 7 children): /// /// /// /// /// /// /// /// /// /// /// /// /// /// ///
ChildChildChild
ChildChild
ChildChild
/// /// * `Horizontal`: The container will fill rows first. **Default.** /// /// Visual example (`ndiv=3` grid with 7 children): /// /// /// /// /// /// /// /// /// /// /// /// /// /// ///
ChildChildChild
ChildChildChild
Child
/// pub fn set_orientation(&mut self, orientation: Orientation) -> &mut Self { self.set_const_str_attribute(::attrs::ORIENTATION, orientation.as_cstr()); self } } impl_widget! { Grid, "matrix" } /// Convert a heterogeneous list of widgets into a `Vec`, /// suitable for passing to any function that takes `AsRef<[BaseWidget]>`, such as a constructor /// for one of the container types. #[macro_export] macro_rules! children [ // Accepts invocation with or without a final comma. ($($child:expr),+,) => (children![$($child),+]); ($($child:expr),+) => ({ use ::kiss_ui::widget::Widget; vec![$($child.to_base()),+] }); () => (vec![]); ]; ================================================ FILE: src/dialog.rs ================================================ //! KISS-UI top-level dialogs (windows) use base::BaseWidget; use widget_prelude::*; use ::iup_sys; use std::ffi::CString; use std::ptr; /// A top-level dialog that can create a new native window when shown, /// and can contain a single widget (which can be a container for many widgets). pub struct Dialog(IUPPtr); impl Dialog { /// Create a new dialog with a single child. /// /// To create a dialog containing multiple widgets, use a struct from the `container` module. /// /// ##Note /// This does **not** make the dialog appear on screen. `.show()` must be called after the /// dialog has been configured. /// /// ##Panics /// If called outside a valid KISS-UI context. pub fn new(contents: W) -> Dialog where W: Widget { assert_kiss_running!(); unsafe { let ptr = iup_sys::IupDialog(contents.ptr()); Self::from_ptr(ptr) } } /// Create a new dialog with no children. /// /// ##Panics /// If called outside a valid KISS-UI context. pub fn empty() -> Dialog { assert_kiss_running!(); unsafe { let ptr = iup_sys::IupDialog(ptr::null_mut()); Self::from_ptr(ptr) } } /// Set the title of this dialog, which will appear in the title bar of the native window. pub fn set_title>(self, title: T) -> Self { self.set_str_attribute(::attrs::TITLE, title); self } /// Set the size of this dialog in pixels. pub fn set_size_pixels(self, width: u32, height: u32) -> Self { let rastersize = format!("{}x{}", width, height); self.set_str_attribute(::attrs::RASTERSIZE, rastersize); self } /// Get a child of this dialog named by `name`. /// /// Returns `None` if the child was not found. pub fn get_child(self, name: &str) -> Option { let name = CString::new(name).unwrap(); unsafe { let child_ptr = iup_sys::IupGetDialogChild(self.ptr(), name.as_ptr()); BaseWidget::from_ptr_opt(child_ptr) } } } impl Destroy for Dialog {} impl_widget! { Dialog, "dialog" } impl_on_show! { Dialog } /// Popup a message dialog and block until it is closed, by either the OK button or the exit /// button. pub fn message_popup, M: Into>(title: T, message: M) { assert_kiss_running!(); let title = CString::new(title.into()).unwrap(); let message = CString::new(message.into()).unwrap(); unsafe { iup_sys::IupMessage(title.as_ptr(), message.as_ptr()); } } /// A builder for an alert dialog that can show a message and up to 3 buttons for the user's /// response. pub struct AlertPopupBuilder { pub title: String, pub message: String, pub button1: String, pub button2: Option, pub button3: Option, } impl AlertPopupBuilder { pub fn new, M: Into, B1: Into>( title: T, message: M, button1: B1 ) -> AlertPopupBuilder { AlertPopupBuilder { title: title.into(), message: message.into(), button1: button1.into(), button2: None, button3: None, } } /// Set the text of the second button pub fn button2>(mut self, button2: B2) -> Self { self.button2 = Some(button2.into()); self } pub fn button3>(mut self, button3: B3) -> Self { self.button3 = Some(button3.into()); self } /// Popup the dialog and block until the user takes an action. /// /// Returns: which button was pressed, or **0** if the dialog was closed. pub fn popup(self) -> i32 { let title = CString::new(self.title).unwrap(); let message = CString::new(self.message).unwrap(); let button1 = CString::new(self.button1).unwrap(); let button2 = self.button2.map(|b2| CString::new(b2).unwrap()); let button3 = self.button3.map(|b3| CString::new(b3).unwrap()); unsafe { iup_sys::IupAlarm( title.as_ptr(), message.as_ptr(), button1.as_ptr(), button2.as_ref().map_or_else(ptr::null, |b2| b2.as_ptr()), button3.as_ref().map_or_else(ptr::null, |b3| b3.as_ptr()), ) } } } ================================================ FILE: src/image.rs ================================================ //! Renderable image buffers. use widget_prelude::*; use base::Downcast; use std::mem; /// An image buffer allocated by IUP. /// /// ##Note: Not a Renderable Widget /// While this type can be dereferenced and converted to `BaseWidget`, it is *not* a renderable /// widget and adding it to a container will have no visual effect. /// /// Instead, it should be set on another widget type that implements the `ImageContainer` trait, /// which will handle the actual rendering. /// /// ##Note: Memory Usage /// This struct should be freed by calling `.destroy()` on it when it is no longer in use. /// Otherwise, it will be freed when `kiss_ui::show_gui()` exits^([citation needed]). /// /// ##Note: Cloning /// Cloning this image does not duplicate its allocation. Thus, destroying one image cloned from /// another will destroy them both. pub struct Image(IUPPtr); impl Image { /// Create a new RGB image buffer from a slice of 3-byte tuples, copying the data into a new /// allocation. /// /// See `transmute_buffer_rgb()` in this module. /// /// ##Panics /// If `width * height` is not equal to `pixels.len()`. pub fn new_rgb(width: u32, height: u32, pixels: &[(u8, u8, u8)]) -> Image { assert_eq!((width * height) as usize, pixels.len()); unsafe { let ptr = ::iup_sys::IupImageRGB(width as i32, height as i32, pixels.as_ptr() as *const u8); Self::from_ptr(ptr) } } /// Create a new RGBA image buffer from a slice of 4-byte tuples, copying the data into a new /// allocation. /// /// See `transmute_buffer_rgba` in this module. /// /// ##Panics /// If `width * height` is not equal to `pixels.len()`. pub fn new_rgba(width: u32, height: u32, pixels: &[(u8, u8, u8, u8)]) -> Image { assert_eq!((width * height) as usize, pixels.len()); unsafe { let ptr = ::iup_sys::IupImageRGBA(width as i32, height as i32, pixels.as_ptr() as *const u8); Self::from_ptr(ptr) } } } impl Destroy for Image {} impl_widget! { Image, ["image", "imagergb", "imagergba"] } /// Cast a slice of bytes to a slice of 3-byte tuples without copying. /// /// Returns `None` if `buf.len()` is not evenly divisible by 3. pub fn transmute_buffer_rgb(buf: &[u8]) -> Option<&[(u8, u8, u8)]> { if buf.len() % 3 == 0 { Some(unsafe { mem::transmute(buf) }) } else { None } } /// Cast a slice of bytes to a slice of 4-byte tuples without copying. /// /// Returns `None` if `buf.len()` is not evenly divisible by 4. pub fn transmute_buffer_rgba(buf: &[u8]) -> Option<&[(u8, u8, u8, u8)]> { if buf.len() % 4 == 0 { Some(unsafe { mem::transmute(buf) }) } else { None } } /// A trait describing an object that can render an image within itself. pub trait ImageContainer: Widget { /// Set the image this widget is to render and return `self` for method chaining. fn set_image(self, image: Image) -> Self { self.set_attr_handle(::attrs::IMAGE, image); self } /// Get a copy of the image set on this widget, if any. fn get_image(&self) -> Option { use base::BaseWidget; self.get_attr_handle(::attrs::IMAGE) .map(BaseWidget::try_downcast::) .and_then(Result::ok) } } ================================================ FILE: src/lib.rs ================================================ //! A UI framework for Rust based on the KISS principle: "Keep It Simple, Stupid!" //! //! Built on top of the [IUP GUI library for C.][iup] //! //! ##Note: "valid KISS-UI context" //! All KISS-UI static widget methods will panic if called before `kiss_ui::show_gui()` is invoked or //! after it returns. //! //! This is because the underlying IUP library has been either, respectively, not initialized yet //! or already deinitialized, and attempting to interact with it in either situation will likely cause //! undefined behavior. //! //! ##Note: This is a (technically) leaky abstraction. //! Because IUP only frees all its allocations when it is deinitialized, all widgets created by KISS-UI //! will remain in-memory until `kiss_ui::show_gui()` returns. While unbounded memory growth can //! happen with complex applications, this should not be an issue for most use-cases. //! //! However, some types *do* allocate large chunks of memory, or other valuable system resources, //! and should be manually freed when they are no longer being used. //! This is most evident with the `Image` struct, which can allocate large backing buffers for image data. //! //! All types that should be manually freed expose a `.destroy()` method which should be called //! when they are no longer being used. This can safely be called multiple times on clones of the //! widget types^([citation needed]). //! //! [iup]: http://webserver2.tecgraf.puc-rio.br/iup/ extern crate libc; extern crate iup_sys; macro_rules! assert_kiss_running ( () => ( assert!( ::KISS_RUNNING.load(::std::sync::atomic::Ordering::Acquire), "No KISS-UI widget methods may be called before `kiss_ui::show_gui()` is invoked or after it returns!" ) ) ); #[macro_use] pub mod widget; #[macro_use] pub mod utils; // Internal use modules mod attrs; // User-facing modules #[macro_use] pub mod callback; pub mod base; pub mod button; pub mod container; pub mod dialog; pub mod image; pub mod progress; pub mod text; pub mod timer; use std::borrow::Borrow; use std::cell::{Cell, RefCell}; use std::collections::HashMap; use std::ptr; use std::rc::Rc; use std::sync::atomic::{AtomicBool, ATOMIC_BOOL_INIT, Ordering}; use base::BaseWidget; use dialog::Dialog; use widget::Widget; use utils::cstr::AsCStr; use widget_prelude::IUPPtr; mod widget_prelude { pub use widget::{Widget, IUPWidget, Destroy, WidgetStr}; pub type IUPPtr = *mut ::iup_sys::Ihandle; } /// A module that KISS-UI users can glob-import to get the most common types. pub mod prelude { pub use base::BaseWidget; pub use dialog::Dialog; pub use container::Orientation; pub use callback::{CallbackStatus, OnClick, OnShow, OnValueChange}; pub use widget::{Widget, Destroy}; } static KISS_RUNNING: AtomicBool = ATOMIC_BOOL_INIT; thread_local! { static CONTEXT: KISSContext = KISSContext::default() } #[derive(Default)] struct KISSContext { widget_store: RefCell>, // FIXME: use Rc<()> once Rc::is_unique stabilizes borrowed_strs: RefCell>>>>, } impl KISSContext { fn assert_str_not_borrowed(widget: IUPPtr, str_: &'static str) { assert_kiss_running!(); let is_borrowed = CONTEXT.with(|context| context.borrowed_strs.borrow() .get(&widget) .and_then(|widget_strs| widget_strs.get(str_) .map(|refcount| refcount.get() != 0) ) .unwrap_or(false) ); assert!( !is_borrowed, "Cannot update the value of a string property of a widget if it's been previously borrowed!" ); } fn str_refcount(widget: IUPPtr, str_: &'static str) -> Rc> { assert_kiss_running!(); CONTEXT.with(|context| context.borrowed_strs.borrow_mut() .entry(widget).or_insert_with(HashMap::new) .entry(str_).or_insert_with(|| Rc::new(Cell::new(0))) .clone() ) } fn store_widget, W: Widget>(name: N, widget: W) -> Option { CONTEXT.with(|context| context.widget_store.borrow_mut() .insert(name.into(), widget.to_base()) ) } fn load_widget>(name: &N) -> Option { CONTEXT.with(|context| context.widget_store.borrow().get(name.borrow()).cloned() ) } unsafe fn clear() { CONTEXT.with(|context| { context.widget_store.borrow_mut().clear(); context.borrowed_strs.borrow_mut().clear(); }) } } /// The entry point for KISS-UI. The closure argument should initialize and call `.show()`. /// /// ##Blocks /// Until all KISS-UI dialogs are closed. /// /// ##Warning /// No static widget methods from this crate may be called before this function is /// invoked or after it returns, with the exception of the closure passed to this function. /// /// While this function is blocked and the IUP event loop is running, any reachable code is /// considered a "valid KISS-UI context" and may create and interact with widgets and dialogs. /// /// After it returns, IUP is deinitialized and all static widget methods will panic to avoid /// undefined behavior. /// /// ##Note: `Send` bound /// This closure will be called in the same thread where `show_gui()` is invoked. No threading is /// involved. /// /// However, without the `Send` bound it would be possible to move widget types outside /// of the closure with safe code and interact with them after IUP has been deinitialized, /// which would cause undefined behavior. /// /// Since no widget types are `Send`, this bound prevents this from happening without requiring /// all widget methods to check if they were invoked in a valid context. pub fn show_gui(init_fn: F) where F: FnOnce() -> Dialog + Send { assert!( !KISS_RUNNING.compare_and_swap(false, true, Ordering::SeqCst), "KISS-UI may only be running (in `kiss_ui::show_gui()`) in one thread at a time!" ); unsafe { assert!(iup_sys::IupOpen(ptr::null(), ptr::null()) == 0); // Force IUP to always use UTF-8 iup_sys::IupSetGlobal(::attrs::UTF8_MODE.as_cstr(), ::attrs::values::YES.as_cstr()); } init_fn().show(); unsafe { iup_sys::IupMainLoop(); iup_sys::IupClose(); KISSContext::clear(); } KISS_RUNNING.store(false, Ordering::SeqCst); } ================================================ FILE: src/progress.rs ================================================ //! Progress bars and dialogs. use widget_prelude::*; use container::Orientation; /// A widget that renders a bar which fills as its set value approaches a maximum. /// /// For more info, see the [`IupProgressBar`][iup-progress] documentation. (Note: "marquee" is the /// same as "indefinite") /// /// [iup-progress]: http://webserver2.tecgraf.puc-rio.br/iup/en/elem/iupprogressbar.html pub struct ProgressBar(IUPPtr); impl ProgressBar { /// Create a new progress bar. pub fn new() -> ProgressBar { unsafe { let ptr = ::iup_sys::IupProgressBar(); Self::from_ptr(ptr) } } /// Set this progress bar as indefinite or not. /// /// In the indefinite state, the progress bar will not /// show its true value; instead it will render a looping animation. /// /// This may not have a visual effect on certain platforms. pub fn set_indefinite(self, is_indefinite: bool) -> Self { self.set_bool_attribute(::attrs::MARQUEE, is_indefinite); self } /// Set if the progress bar should render solid (`false`) or dashed (`true`). /// /// This may not have a visual effect on certain platforms. pub fn set_dashed(self, dashed: bool) -> Self { self.set_bool_attribute(::attrs::DASHED, dashed); self } /// Set the maximum value of this progress bar, i.e. the value at which it will show full. /// /// Defaults to `1.0`. pub fn set_max(self, max: f32) -> Self { self.set_float_attribute(::attrs::MAX, max); self } /// Set the minimum value of this progress bar, i.e. the value at which it will be empty. /// /// Defaults to `0.0`. pub fn set_min(self, min: f32) -> Self { self.set_float_attribute(::attrs::MIN, min); self } /// Set the orientation of this progress bar. /// /// * `Vertical`: The progress bar will render as a vertical bar, and fill from bottom to top. /// * `Horizontal`: The progress bar will render as a horizontal bar, and fill from left to /// right. pub fn set_orientation(self, orientation: Orientation) -> Self { self.set_const_str_attribute(::attrs::ORIENTATION, orientation.as_cstr()); self } /// Set the current value of this progress bar. Its rendered infill will be updated to reflect /// the new value in relation to the minimum and maximum. pub fn set_value(self, val: f32) -> Self { self.set_float_attribute(::attrs::VALUE, val); self } /// Get the current value. pub fn get_value(self) -> f32 { self.get_float_attribute(::attrs::VALUE) } /// Add `amt` to the current value and update it. `amt` may be negative. pub fn add_value(self, amt: f32) -> Self { let val = self.get_float_attribute(::attrs::VALUE); self.set_float_attribute(::attrs::VALUE, val + amt); self } } impl_widget! { ProgressBar, "progressbar" } ================================================ FILE: src/text.rs ================================================ //! Widgets that can render and process text (labels, text boxes). use widget_prelude::*; use std::ffi::CString; use std::ptr; /// A static widget that renders text within its parent. pub struct Label(IUPPtr); impl Label { /// Create a label with some text. pub fn new>(text: S) -> Label { let c_text = CString::new(text.into()).unwrap(); unsafe { let ptr = ::iup_sys::IupLabel(c_text.as_ptr()); Self::from_ptr(ptr) } } /// Create a blank label. The text can be set later. pub fn new_empty() -> Label { unsafe { let ptr = ::iup_sys::IupLabel(ptr::null()); Self::from_ptr(ptr) } } /// Update the text of this label. /// /// ##Panics /// If any `WidgetStr` instances from `self.get_text()` are still reachable. pub fn set_text(self, text: &str) -> Self { self.set_str_attribute(::attrs::TITLE, text); self } /// Get the text of this label. pub fn get_text(&self) -> WidgetStr { self.get_str_attribute(::attrs::TITLE) .expect("This widget should have a text pointer even if it's empty!") } } impl_widget! { Label, "label" } impl ::image::ImageContainer for Label {} /// A widget that renders user-editable text. pub struct TextBox(IUPPtr); impl TextBox { /// Create a new, empty text box. pub fn new() -> TextBox { unsafe { let ptr = ::iup_sys::IupText(ptr::null()); Self::from_ptr(ptr) } } /// Set if the text box should accept and render newline characters. /// /// If `false`, it will only be slightly taller than a line of text in the current font. /// If `true`, the total dimensions will be set by `set_visible_columns` and /// `set_visible_lines`. Text outside these bounds will be accessible with a scrollbar. pub fn set_multiline(self, multiline: bool) -> Self { self.set_bool_attribute(::attrs::MULTILINE, multiline); self } /// Set the rendered width of the textbox in columns (character width + padding). /// /// If the textbox is set as multiline, this will cause additional text beyond the maximum /// width to wrap. Otherwise, it can be scrolled only horizontally. pub fn set_visible_columns(self, cols: u32) -> Self { self.set_int_attribute(::attrs::VISIBLE_COLUMNS, cols as i32); self } /// Set the rendered height of the textbox in lines (character height + padding). /// /// If the textbox is set as multiline, newline characters will push text following them to the /// next visible line. Line counts beyond these bounds will cause a scrollbar to be shown. pub fn set_visible_lines(self, lines: u32) -> Self { self.set_int_attribute(::attrs::VISIBLE_LINES, lines as i32); self } /// Set the text of this textbox. /// /// ##Panics /// If any `WidgetStr` instances from `self.get_text()` are still reachable. pub fn set_text(self, value: &str) -> Self { self.set_str_attribute(::attrs::VALUE, value); self } /// Get the text value of this textbox. pub fn get_text(&self) -> WidgetStr { self.get_str_attribute(::attrs::VALUE) .expect("This string should be present even if it's empty!") } } impl_widget! { TextBox, "text" } impl_on_value_change! { TextBox } ================================================ FILE: src/timer.rs ================================================ //! Timers that can invoke a callback on an interval. use widget_prelude::*; use ::callback::Callback; /// A timer that can invoke a callback on a configurable interval. /// /// ##Note: Not a Renderable Widget /// While this type can be dereferenced and converted to `BaseWidget`, it is *not* a renderable /// widget and adding it to a container will have no visual effect. /// /// ##Note: Resource Usage /// This struct should be freed by calling `.destroy()` on it when it is no longer in use to free /// any resources it has allocated. Otherwise, it will be freed when `kiss_ui::show_gui()` returns. pub struct Timer(IUPPtr); impl Timer { /// Create a new timer with a default interval. /// /// TODO: Document default interval. pub fn new() -> Timer { unsafe { let ptr = ::iup_sys::IupTimer(); Self::from_ptr(ptr) } } /// Set the timer interval in milliseconds. pub fn set_interval(self, time: u32) -> Self { self.set_int_attribute(::attrs::TIME, time as i32); self } /// Set a callback to be invoked when the timer interval elapses. /// The callback will be invoked on every interval until `.stop()` is called. pub fn set_on_interval(self, on_interval: Cb) -> Self where Cb: Callback { callback_impl! { ::attrs::ACTION_CB, self, on_interval, Timer } self } /// Start the timer. The callback will be invoked when the next interval elapses. pub fn start(self) -> Self { self.set_bool_attribute(::attrs::RUN, true); self } /// Stop the timer. The callback will not be invoked until the timer is restarted. pub fn stop(self) -> Self { self.set_bool_attribute(::attrs::RUN, false); self } } impl_widget! { Timer, "timer" } impl Destroy for Timer {} ================================================ FILE: src/utils/cstr.rs ================================================ pub trait AsCStr { fn as_cstr(&self) -> *const ::libc::c_char; } impl AsCStr for &'static str { fn as_cstr(&self) -> *const ::libc::c_char { let bytes = self.as_bytes(); assert!(bytes[bytes.len() - 1] == 0u8); bytes.as_ptr() as *const ::libc::c_char } } macro_rules! cstr ( ($val:expr) => ( concat!($val, "\0") ) ); macro_rules! c_str_const ( ($name:ident = $val:expr) => ( pub const $name: &'static str = cstr!($val); ) ); macro_rules! c_str_consts { ($($name:ident = $val:expr),+,) => ( $(c_str_const!($name = $val);)+ ) } ================================================ FILE: src/utils/mod.rs ================================================ //! Utility types and functions that might be useful for KISS-UI users. #[doc(hidden)] #[macro_use] pub mod cstr; pub mod move_cell; ================================================ FILE: src/utils/move_cell.rs ================================================ //! A cell type that can move values into and out of a shared reference. //! //! Behaves like `RefCell>` but optimized for use-cases where temporary or permanent //! ownership is required. use std::cell::UnsafeCell; use std::mem; /// A cell type that can move values into and out of a shared reference. pub struct MoveCell(UnsafeCell>); impl MoveCell { /// Create a new `MoveCell` with no contained value. pub fn new() -> MoveCell { MoveCell(UnsafeCell::new(None)) } /// Create a new `MoveCell` with the given value. pub fn with(val: T) -> MoveCell { MoveCell(UnsafeCell::new(Some(val))) } /// Create a new `MoveCell` around the given `Option`. pub fn from(opt: Option) -> MoveCell { MoveCell(UnsafeCell::new(opt)) } unsafe fn as_mut(&self) -> &mut Option { &mut *self.0.get() } unsafe fn as_ref(&self) -> &Option { & *self.0.get() } /// Place a value into this `MoveCell`, returning the previous value, if present. pub fn put(&self, val: T) -> Option { mem::replace(unsafe { self.as_mut() }, Some(val)) } /// Take the value out of this `MoveCell`, leaving nothing in its place. pub fn take(&self) -> Option { unsafe { self.as_mut().take() } } /// Take the value out of this `MoveCell`, leaving a clone in its place. pub fn clone_inner(&self) -> Option where T: Clone { let inner = self.take(); inner.clone().map(|inner| self.put(inner)); inner } /// Check if this `MoveCell` contains a value or not. pub fn has_value(&self) -> bool { unsafe { self.as_ref().is_some() } } } impl Default for MoveCell { fn default() -> Self { MoveCell::new() } } ================================================ FILE: src/widget.rs ================================================ //! Operations common to all widget types. use utils::cstr::AsCStr; use base::{BaseWidget, Downcast}; use dialog::Dialog; use widget_prelude::IUPPtr; use ::KISSContext; use iup_sys; use std::borrow::Borrow; use std::cell::Cell; use std::ffi::{CStr, CString}; use std::fmt::{self, Debug, Display, Formatter}; use std::hash::{Hash, Hasher}; use std::ops::Deref; use std::ptr; use std::rc::Rc; /// Trait implemented for all widget types. /// /// Some methods may not apply to some widgets. pub trait Widget: IUPWidget { /// Show this widget if it was previously hidden. /// /// Does nothing if the widget is already shown, or if the operation does not apply. fn show(self) -> Self { unsafe { iup_sys::IupShow(self.ptr()); } self } /// Hide this widget if it was previously visible. /// /// Does nothing if the widget is already hidden, or if the operation does not apply. fn hide(self) -> Self { unsafe { iup_sys::IupHide(self.ptr()); } self } /// Set the widget's visibility state. /// /// `.set_visible(true)` is equivalent to calling `.show()`, and `.set_visible(false)` /// is equivalent to calling `.hide()`. /// /// Does nothing if the widget is in the same visibility state as the one being set, /// or if the operation does not apply. fn set_visible(self, visible: bool) -> Self { self.set_bool_attribute(::attrs::VISIBLE, visible); self } /// Set the widget's enabled state. /// /// When a widget is disabled, it does not react to user interaction or invoke any callbacks. /// /// Does nothing if the widget does not support being disabled. fn set_enabled(self, enabled: bool) -> Self { self.set_bool_attribute(::attrs::ACTIVE, enabled); self } /// Set the position of this widget relative to the top-left corner of its parent. /// /// Does nothing if the widget is not renderable or not attached to a parent. fn set_position(self, x: i32, y: i32) -> Self { self.set_str_attribute(::attrs::POSITION, format!("{x},{y}", x=x, y=y)); self } /// Get the position of this widget relative to the top-left corner of its parent. /// /// Returns (0, 0) if the widget is not renderable, not attached to a parent, or if that is the /// widget's actual relative position. fn get_position(self) -> (i32, i32) { self.get_int2_attribute(::attrs::POSITION) } /// Set the name of the widget so it can be found within its parent. /// /// Does nothing if the widget does not support having a name. /// /// ##Panics /// If any `WidgetStr` instances from `self.get_name()` are still reachable. fn set_name(self, name: &str) -> Self { self.set_str_attribute(::attrs::NAME, name); self } /// Get the name of this widget, if the widget supports having a name and one is set. fn get_name(&self) -> Option { self.get_str_attribute(::attrs::NAME) } /// Get the next child in the parent after this widget, based on the order in which they were /// added. /// /// Returns `None` if this widget is an only child or is not attached to a parent. fn get_sibling(self) -> Option { unsafe { let ptr = iup_sys::IupGetBrother(self.ptr()); BaseWidget::from_ptr_opt(ptr) } } /// Get the parent of this widget. /// /// Returns `None` if this widget has no parent. fn get_parent(self) -> Option { unsafe { let ptr = iup_sys::IupGetParent(self.ptr()); BaseWidget::from_ptr_opt(ptr) } } /// Get the containing dialog of this widget. /// /// Returns `None` if this widget is not attached to a dialog. fn get_dialog(self) -> Option { unsafe { let ptr = iup_sys::IupGetDialog(self.ptr()); // Note to self: not using UFCS because `downcast()` is an unsafe function. BaseWidget::from_ptr_opt(ptr).map(|base| Dialog::downcast(base)) } } /// Get the rendered size of this widget, in pixels. /// /// Returns `(0, 0)` if this widget has no rendered size. fn get_size_pixels(self) -> (u32, u32) { let (width, height) = self.get_int2_attribute(::attrs::RASTERSIZE); (width as u32, height as u32) } /// Store this widget under `name`, returning the previous widget stored, if any. /// /// It may later be retrieved from any valid KISS-UI context /// by calling `BaseWidget::load(name)`. fn store>(self, name: N) -> Option { KISSContext::store_widget(name, self) } fn to_base(self) -> BaseWidget { unsafe { BaseWidget::from_ptr(self.ptr()) } } } pub trait Destroy: Widget { fn destroy(self) { unsafe { iup_sys::IupDestroy(self.ptr()); } } } /// A string slice borrowed from a widget's metadata. Can be dereferenced to `&str`. /// /// Tracks ownership of the string so that its pointer cannot be invalidated. Releases ownership /// on-drop. pub struct WidgetStr<'a> { refcount: Rc>, data: &'a str, } impl<'a> WidgetStr<'a> { pub fn new(ptr: *mut iup_sys::Ihandle, name: &'static str, str_data: &'a str) -> WidgetStr<'a> { let widgetStr = WidgetStr { refcount: KISSContext::str_refcount(ptr, name), data: str_data, }; widgetStr.inc_refcount(); widgetStr } fn inc_refcount(&self) { let refcount = self.refcount.get(); self.refcount.set(refcount + 1); } } impl<'a> Borrow for WidgetStr<'a> { fn borrow(&self) -> &str { self.data } } impl<'a> Debug for WidgetStr<'a> { fn fmt(&self, fmt: &mut Formatter) -> Result<(), fmt::Error> { ::fmt(self.data, fmt) } } impl<'a> Display for WidgetStr<'a> { fn fmt(&self, fmt: &mut Formatter) -> Result<(), fmt::Error> { ::fmt(self.data, fmt) } } impl<'a> Hash for WidgetStr<'a> { fn hash(&self, hasher: &mut H) where H: Hasher { self.data.hash(hasher) } } impl<'a> Deref for WidgetStr<'a> { type Target = str; fn deref<'b>(&'b self) -> &'b str { self.data } } impl<'a> Clone for WidgetStr<'a> { fn clone(&self) -> Self { self.inc_refcount(); WidgetStr { refcount: self.refcount.clone(), data: self.data, } } } impl<'a> Drop for WidgetStr<'a> { fn drop(&mut self) { let refcount = self.refcount.get(); self.refcount.set(refcount - 1); } } #[doc(hidden)] pub trait IUPWidget: Copy { unsafe fn from_ptr(ptr: IUPPtr) -> Self; unsafe fn from_ptr_opt(ptr: IUPPtr) -> Option { if !ptr.is_null() { Some(Self::from_ptr(ptr)) } else { None } } fn ptr(self) -> IUPPtr; fn classname(&self) -> &CStr { unsafe { CStr::from_ptr(iup_sys::IupGetClassName(self.ptr())) } } fn set_str_attribute(self, name: &'static str, val: V) where V: Into { KISSContext::assert_str_not_borrowed(self.ptr(), name); let c_val = CString::new(val.into()).unwrap(); unsafe { iup_sys::IupSetStrAttribute(self.ptr(), name.as_cstr(), c_val.as_ptr()); } } fn set_opt_str_attribute(self, name: &'static str, val: Option) where V: Into { KISSContext::assert_str_not_borrowed(self.ptr(), name); let c_val = val.map(V::into).map(CString::new).map(Result::unwrap); unsafe { iup_sys::IupSetStrAttribute( self.ptr(), name.as_cstr(), // This looks backwards, but check the docs. It's right. c_val.as_ref().map_or_else(ptr::null, |c_val| c_val.as_ptr()) ) } } fn set_const_str_attribute(self, name: &'static str, val: &'static str) { KISSContext::assert_str_not_borrowed(self.ptr(), name); unsafe { iup_sys::IupSetAttribute(self.ptr(), name.as_cstr(), val.as_cstr()); } } fn get_str_attribute(&self, name: &'static str) -> Option { let ptr = unsafe { iup_sys::IupGetAttribute(self.ptr(), name.as_cstr()) }; if !ptr.is_null() { unsafe { // Safe since we're controlling the lifetime let c_str = CStr::from_ptr(ptr); // We're forcing IUP to use UTF-8 let str_data = ::std::str::from_utf8_unchecked(c_str.to_bytes()); Some(WidgetStr::new(self.ptr(), name, str_data)) } } else { None } } fn set_int_attribute(self, name: &'static str, val: i32) { unsafe { iup_sys::IupSetInt(self.ptr(), name.as_cstr(), val); } } fn get_int_attribute(self, name: &'static str) -> i32 { unsafe { iup_sys::IupGetInt(self.ptr(), name.as_cstr()) } } fn get_int2_attribute(self, name: &'static str) -> (i32, i32) { let mut left = 0; let mut right = 0; unsafe { assert!(iup_sys::IupGetIntInt(self.ptr(), name.as_cstr(), &mut left, &mut right) != 0); } (left, right) } fn set_float_attribute(self, name: &'static str, val: f32) { unsafe { iup_sys::IupSetFloat(self.ptr(), name.as_cstr(), val); } } fn get_float_attribute(self, name: &'static str) -> f32 { unsafe { iup_sys::IupGetFloat(self.ptr(), name.as_cstr()) } } fn set_bool_attribute(self, name: &'static str, val: bool) { let val = ::attrs::values::bool_yes_no(val); self.set_const_str_attribute(name, val); } fn set_attr_handle(self, name: &'static str, handle: W) { unsafe { iup_sys::IupSetAttributeHandle(self.ptr(), name.as_cstr(), handle.ptr()); } } fn get_attr_handle(self, name: &'static str) -> Option { unsafe { let existing = iup_sys::IupGetAttributeHandle(self.ptr(), name.as_cstr()); BaseWidget::from_ptr_opt(existing) } } fn set_callback(self, name: &'static str, callback: ::iup_sys::Icallback) { unsafe { iup_sys::IupSetCallback(self.ptr(), name.as_cstr(), callback); } } } impl<'a, T: IUPWidget> IUPWidget for &'a T { unsafe fn from_ptr(_ptr: *mut iup_sys::Ihandle) -> Self { panic!("Cannot construct an &mut Self from a pointer"); } fn ptr(self) -> *mut iup_sys::Ihandle { (*self).ptr() } } macro_rules! impl_widget { ($ty:ident, [$($classname:expr),+]) => { impl_widget!($ty); impl ::base::Downcast for $ty { fn can_downcast(base: &::base::BaseWidget) -> bool { [$($classname.as_bytes()),+].contains(&base.classname().to_bytes()) } } }; ($ty:ident, $classname:expr) => { impl_widget!($ty); impl ::base::Downcast for $ty { fn can_downcast(base: &::base::BaseWidget) -> bool { $classname.as_bytes() == base.classname().to_bytes() } } }; ($ty:ident) => { impl ::widget::IUPWidget for $ty { unsafe fn from_ptr(ptr: ::widget_prelude::IUPPtr) -> Self { assert!( !ptr.is_null(), concat!( concat!("Failed to construct ", stringify!($ty)), "; pointer returned from IUP was null!" ) ); $ty(ptr) } fn ptr(self) -> ::widget_prelude::IUPPtr { self.0 } } impl ::widget::Widget for $ty {} impl Copy for $ty {} impl Clone for $ty { fn clone(&self) -> Self { *self } } } }