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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@

## Unreleased
- Migrated to Webpack 5 for async WASM and modern JS tooling.
- Dropped stdweb; all browser interop now uses web-sys, wasm-bindgen, and gloo-timers.
- Updated Svelte/Webpack config for compatibility and modern plugin usage.

## v0.4
- (breaking) Changed type of positions from `u32` to `i32` (for pane and global frame). Negative offsets are valid and sometimes necessary.
3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,5 @@ features = [

[dev-dependencies]
wasm-bindgen-test = "0.3"
wasm-bindgen-futures = "0.4"
wasm-bindgen-futures = "0.4"

54 changes: 47 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,16 +25,56 @@ The features can be summarized as:

The examples in this crate are hosted online: [div-rs Examples](https://div.paddlers.ch/)

Have a look at the code in example directory. The best way is to clone the repository and run it locally, so you can play around with the code.
Below is an example of the output you can generate with the built-in GIF capture tool:

You need npm, webpack, and wasm-pack for the examples to run on your machine.
```
git clone https://github.com/jakmeier/div-rs.git
cd div-rs/examples/www;
npm run build;
npm run start;
![Animated Example Output](./div-rs-captured-frames.gif)

Have a look at the code in the example directory. The best way is to clone the repository and run it locally, so you can play around with the code and generate your own GIFs.


## Requirements (2025+)

You need Node.js (16+ recommended), npm, and [wasm-pack](https://rustwasm.github.io/wasm-pack/installer/) for the examples to run on your machine.

**This project now uses Webpack 5 for modern WebAssembly support.**

### Setup and Usage

1. Clone the repository:
```sh
git clone https://github.com/jakmeier/div-rs.git
cd div-rs/examples/www
```
2. Install dependencies (this will install Webpack 5 and all required loaders/plugins):
```sh
npm install
```
3. Build all Rust WASM examples and bundle with Webpack:
```sh
npm run build
```
4. Start the development server:
```sh
npm start
```
5. Open your browser to the address shown in the terminal (usually http://localhost:8080).

### Cleaning builds

To clean all Rust and JS build artifacts:
```sh
npm run clean
```

### Notable Changes (2025)

- **Webpack 5 migration:** The project now uses Webpack 5 for native async WebAssembly support. All config and dependencies have been updated.
- **No more stdweb:** All examples now use `web-sys`, `wasm-bindgen`, and (where needed) `gloo-timers` for browser interop. `stdweb` is no longer supported.
- **Svelte loader:** The Webpack config now includes `resolve.conditionNames` for Svelte 3+ compatibility.
- **CopyWebpackPlugin:** Updated to use the new `patterns` API.

If you see WASM loader or Svelte warnings, make sure you have the latest dependencies and configs as above.

## Origin and Motivation
My motivation to create Div was to leverage HTML + CSS when using Rust to create games for a browser.
Prior to this, the only way I knew how to do a GUI easily (in Rust running on the browser) was to render everything through general-purpose GUI crates with a WebGL backend. This seemed a bit wasteful to me, as the browser already has excellent built-in support for GUIs.
Expand Down
Binary file added div-rs-captured-frames.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 3 additions & 2 deletions examples/hello_svelte/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@
[package]
name = "hello_svelte"
version = "0.1.0"
authors = ["Jakob Meier <[email protected]>"]
authors = ["Jakob Meier <[email protected]>", "David Horner"]
edition = "2018"

[lib]
crate-type = ["cdylib", "rlib"]

[dependencies]
div = { path = "../../" }
gloo-timers = "0.3.0"
wasm-bindgen = "0.2"
wasm-bindgen-futures = "0.4"

Expand All @@ -22,4 +23,4 @@ features = [
"Window",
"HtmlScriptElement",
"HtmlHeadElement",
]
]
25 changes: 17 additions & 8 deletions examples/hello_svelte/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,24 @@
use wasm_bindgen::prelude::*;

#[wasm_bindgen(start)]
#[wasm_bindgen]
pub fn main() {
div::init_to("div-root").unwrap();
set_panic_hook();

// Create a new pane at offset (100,100) from body
// with size 500px/500px and then create a single
// text node inside it with an external class stored in TODO
// Defensive: check for double initialization
match div::init_to("div-root") {
Ok(_) => {},
Err(e) => {
panic!("[hello_svelte] div::init_to('div-root') failed: {e:?}.\nThis usually means main() was called without a prior reset, or reset did not complete before main().");
}
}

const X: u32 = 0;
const Y: u32 = 0;
const W: u32 = 500;
const H: u32 = 500;
let class = div::JsClass::preregistered("MyComponent")
.expect("JS class Test has not been registered properly");
div::from_js_class(X, Y, W, H, class).unwrap();
.unwrap_or_else(|| panic!("[hello_svelte] Svelte component 'MyComponent' is not registered.\n\nMake sure register_svelte_component('MyComponent', ...) is called in JS before calling main()."));
div::from_js_class(X as i32, Y as i32, W, H, class).expect("[hello_svelte] div::from_js_class failed");

/* Alternative that loads classes from a separate JS file instead of registering in the JS code. */
// let future = async {
Expand All @@ -24,13 +28,18 @@ pub fn main() {
// wasm_bindgen_futures::spawn_local(future);
}

#[wasm_bindgen]
pub fn reset() {
div::reset_global_div_state();
}

pub fn set_panic_hook() {
// When the `console_error_panic_hook` feature is enabled, we can call the
// `set_panic_hook` function at least once during initialization, and then
// we will get better error messages if our code ever panics.
//
// For more details see
// https://github.com/rustwasm/console_error_panic_hook#readme
#[cfg(feature = "console_error_panic_hook")]
#[cfg(feature = "console_error_panic_hook")]
console_error_panic_hook::set_once();
}
10 changes: 7 additions & 3 deletions examples/hello_world/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
#![allow(unused_must_use)]
use wasm_bindgen::prelude::*;

#[wasm_bindgen(start)]
pub fn main() {
div::init_to("div-root");
div::init_to("div-root").expect("Init failed");

// Create a new pane at offset (100,100) from body
// with size 500px/500px and then create a single
Expand All @@ -13,5 +12,10 @@ pub fn main() {
let w = 500;
let h = 500;
let html = "Hello world";
div::new(x, y, w, h, html);
div::new(x, y, w, h, html).unwrap();
}

#[wasm_bindgen]
pub fn reset() {
div::reset_global_div_state();
}
7 changes: 5 additions & 2 deletions examples/reposition/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,17 @@
[package]
name = "reposition"
version = "0.1.0"
authors = ["Jakob Meier <[email protected]>"]
authors = ["Jakob Meier <[email protected]>", "David Horner"]
edition = "2018"

[lib]
crate-type = ["cdylib", "rlib"]


[dependencies]
console_error_panic_hook = "0.1.7"
div = { path = "../../" }
wasm-bindgen = "0.2"
stdweb = "0.4"

[dependencies.web-sys]
version = "0.3"
Expand All @@ -22,4 +23,6 @@ features = [
"Window",
"HtmlScriptElement",
"HtmlHeadElement",
"Event",
"KeyboardEvent",
]
132 changes: 93 additions & 39 deletions examples/reposition/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use stdweb::js;
use stdweb::traits::*;
use stdweb::web::event::KeyDownEvent;
use wasm_bindgen::JsCast;
use web_sys::window;
use wasm_bindgen::prelude::*;
use console_error_panic_hook;

/**
* This example show how
Expand All @@ -20,8 +20,47 @@ use wasm_bindgen::prelude::*;
* repositioned and/or resized, it can also change the arrangement of the internal HTML elements.
*/

static mut KEYDOWN_CLOSURE: Option<Closure<dyn FnMut(web_sys::Event)>> = None;

#[wasm_bindgen]
pub fn reset() {
// Remove the keydown event listener if present
let win = match window() {
Some(w) => w,
None => return,
};
unsafe {
if let Some(old_closure) = KEYDOWN_CLOSURE.take() {
let _ = win.remove_event_listener_with_callback("keydown", old_closure.as_ref().unchecked_ref());
// drop(old_closure); // dropped automatically
}
}
// Also reset the Rust global state so example can be re-initialized
div::reset_global_div_state();

// Explicitly clear all children of div-root (DOM cleanup)
if let Some(doc) = web_sys::window().and_then(|w| w.document()) {
if let Some(div_root) = doc.get_element_by_id("div-root") {
while let Some(child) = div_root.first_child() {
let _ = div_root.remove_child(&child);
}
}
}
}

#[wasm_bindgen(start)]
pub fn main() {
// Enable better panic messages
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

needs to go...

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, please remove this here and also remove the added dependency in this example

console_error_panic_hook::set_once();

// Remove previous keydown listener if present
let win = window().unwrap();
unsafe {
if let Some(old_closure) = KEYDOWN_CLOSURE.take() {
let _ = win.remove_event_listener_with_callback("keydown", old_closure.as_ref().unchecked_ref());
// drop(old_closure); // dropped automatically
}
}

// Start at position (0,0) with size (350,200)
let mut x = 0;
Expand Down Expand Up @@ -53,45 +92,60 @@ pub fn main() {
let _pane_b = div::new(200, 50, 100, 100, html2).unwrap();

// Define control variables for zoom of global area and pane A
let mut f = 1.0;
let mut af = 1.0;
let mut f: f32 = 1.0;
let mut af: f32 = 1.0;

// We are using webstd here to make things easy.
// Listen to arrow key to move and reposition all div
stdweb::web::document().add_event_listener(move |e: KeyDownEvent| {
match e.key().as_str() {
"ArrowUp" => y = y.saturating_sub(10),
"ArrowDown" => y += 10,
"ArrowLeft" => x = x.saturating_sub(10),
"ArrowRight" => x += 10,
"+" => f *= 1.5,
"-" => f /= 1.5,
// Listen to keydown events to move and reposition all divs (with bounds checks)
let closure = Closure::wrap(Box::new(move |event: web_sys::Event| {
let keyboard_event = event.dyn_ref::<web_sys::KeyboardEvent>();
if let Some(e) = keyboard_event {
let key = e.key();
match key.as_str() {
"ArrowUp" => { y = y.saturating_sub(10); },
"ArrowDown" => { y += 10; },
"ArrowLeft" => { x = x.saturating_sub(10); },
"ArrowRight" => { x += 10; },
"+" => { f *= 1.5; },
"-" => { f /= 1.5; },

"w" => ay = ay.saturating_sub(10),
"a" => ax = ax.saturating_sub(10),
"s" => ay += 10,
"d" => ax += 10,
"1" => af *= 1.5,
"2" => af /= 1.5,
"w" => { ay = ay.saturating_sub(10); },
"a" => { ax = ax.saturating_sub(10); },
"s" => { ay += 10; },
"d" => { ax += 10; },
"1" => { af *= 1.5; },
"2" => { af /= 1.5; },

key => {
js! { @(no_return) console.log("pressed " + @{key}); };
return;
_ => {
web_sys::console::log_1(&format!("pressed {}", key).into());
return;
}
}
// Bounds checks to prevent panics
let safe_x = x.max(0);
let safe_y = y.max(0);
let safe_f = f.max(0.1); // Prevent zero/negative scale
let safe_af = af.max(0.1);
let safe_ax = ax.max(0);
let safe_ay = ay.max(0);
let safe_aw = (safe_af * aw as f32).max(1.0);
let safe_ah = (safe_af * ah as f32).max(1.0);
let safe_w = (safe_f * w as f32).max(1.0);
let safe_h = (safe_f * h as f32).max(1.0);

div::reposition(safe_x, safe_y).unwrap();
div::resize(safe_w as u32, safe_h as u32).unwrap();
pane_a
.reposition_and_resize(safe_ax, safe_ay, safe_aw as u32, safe_ah as u32)
.unwrap();
// Same as
// pane_a.reposition(ax,ay).unwrap();
// pane_a.resize(aw as u32, ah as u32).unwrap();
// but avoids extra redraw of div
}
div::reposition(x, y).unwrap();
let w = f * w as f32;
let h = f * h as f32;
div::resize(w as u32, h as u32).unwrap();
}) as Box<dyn FnMut(_)>);

let aw = af * aw as f32;
let ah = af * ah as f32;
pane_a
.reposition_and_resize(ax, ay, aw as u32, ah as u32)
.unwrap();
// Same as
// pane_a.reposition(ax,ay).unwrap();
// pane_a.resize(aw as u32, ah as u32).unwrap();
// but avoids extra redraw of div
});
}
win.add_event_listener_with_callback("keydown", closure.as_ref().unchecked_ref()).unwrap();
unsafe {
KEYDOWN_CLOSURE = Some(closure);
}
}
5 changes: 3 additions & 2 deletions examples/styled/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,16 @@
[package]
name = "styled"
version = "0.1.0"
authors = ["Jakob Meier <[email protected]>"]
authors = ["Jakob Meier <[email protected]>", "David Horner"]
edition = "2018"

[lib]
crate-type = ["cdylib", "rlib"]


[dependencies]
div = { path = "../../" }
wasm-bindgen = "0.2"
stdweb = "0.4"

[dependencies.web-sys]
version = "0.3"
Expand All @@ -22,4 +22,5 @@ features = [
"Window",
"HtmlScriptElement",
"HtmlHeadElement",
"HtmlStyleElement",
]
Loading