diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 14b260d9dd..43663b45ee 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -223,6 +223,16 @@ jobs: - name: wasm-pack build examples run: wasm-pack build --dev --target web druid/examples/wasm + # Clippy and build the hello_wasm_web example + - name: cargo clippy hello_wasm_web example + uses: actions-rs/cargo@v1 + with: + command: clippy + args: --manifest-path=druid/examples/hello_wasm_web/Cargo.toml --target wasm32-unknown-unknown -- -D warnings + + - name: wasm-pack build hello_wasm_web example + run: wasm-pack build --target web druid/examples/hello_wasm_web + test-nightly: runs-on: ${{ matrix.os }} strategy: diff --git a/Cargo.toml b/Cargo.toml index 790c26d3f8..1d20d9d82e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,4 +5,5 @@ members = [ "druid-derive", "docs/book_examples", "druid/examples/wasm", + "druid/examples/hello_wasm_web", ] diff --git a/druid/examples/hello_wasm_web/Cargo.toml b/druid/examples/hello_wasm_web/Cargo.toml new file mode 100644 index 0000000000..f4f8c35772 --- /dev/null +++ b/druid/examples/hello_wasm_web/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "hello-wasm-web" +version = "0.1.0" +license = "Apache-2.0" +description = "Minimal wasm example for web" +repository = "https://github.com/xi-editor/druid" +edition = "2018" +publish = false + +[lib] +crate-type = ["cdylib", "rlib"] + +[dependencies] +druid = { path="../.."} +wasm-bindgen = "0.2.60" +console_error_panic_hook = { version = "0.1.6" } diff --git a/druid/examples/hello_wasm_web/README.md b/druid/examples/hello_wasm_web/README.md new file mode 100644 index 0000000000..a86df19fb1 --- /dev/null +++ b/druid/examples/hello_wasm_web/README.md @@ -0,0 +1,29 @@ +# druid WASM hello world + +This is a minimal example of building a single druid application for the web. To build all the druid examples as wasm, check out the main wasm example. + +## Building + +You will need `cargo` and `wasm-pack` for building the code and a simple +server like [`http`](https://crates.io/crates/https) for serving the web page. + +First build with. + +``` +> wasm-pack build --target web --dev +``` + +This generates a JavaScript module that exports the `wasm_main` function that's been annotated with the `#[wasm_bindgen]` macro. Leave off the `--dev` flag if you're doing a release build. + +Now run + +``` +> http +``` + +which should start serving this folder. + +Finally, point your browser to the appropriate localhost url (usually http://localhost:8000) and you +should see your app. + +When you make changes to the project, re-run `wasm-pack build --target web --dev` and you can see the changes in your browser when you refresh -- no need to restart `http`. \ No newline at end of file diff --git a/druid/examples/hello_wasm_web/index.html b/druid/examples/hello_wasm_web/index.html new file mode 100644 index 0000000000..60f5731254 --- /dev/null +++ b/druid/examples/hello_wasm_web/index.html @@ -0,0 +1,27 @@ + + + + + + Druid WASM example + + + + + + + + + + \ No newline at end of file diff --git a/druid/examples/hello_wasm_web/index.js b/druid/examples/hello_wasm_web/index.js new file mode 100644 index 0000000000..3207dd9121 --- /dev/null +++ b/druid/examples/hello_wasm_web/index.js @@ -0,0 +1,8 @@ +import init, { wasm_main } from "./pkg/hello_wasm_web.js"; + +async function run() { + await init(); + wasm_main(); +} + +run(); diff --git a/druid/examples/hello_wasm_web/src/lib.rs b/druid/examples/hello_wasm_web/src/lib.rs new file mode 100644 index 0000000000..2c2e877e47 --- /dev/null +++ b/druid/examples/hello_wasm_web/src/lib.rs @@ -0,0 +1,72 @@ +// Copyright 2020 The xi-editor Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use druid::widget::{Align, Flex, Label, TextBox}; +use druid::{AppLauncher, Data, Env, Lens, Widget, WidgetExt, WindowDesc}; + +use wasm_bindgen::prelude::*; + +const VERTICAL_WIDGET_SPACING: f64 = 20.0; +const TEXT_BOX_WIDTH: f64 = 200.0; + +#[derive(Clone, Data, Lens)] +struct HelloState { + name: String, +} + +// This wrapper function is the primary modification we're making to the vanilla +// hello.rs example. +#[wasm_bindgen] +pub fn wasm_main() { + // This hook is necessary to get panic messages on wasm32 + std::panic::set_hook(Box::new(console_error_panic_hook::hook)); + main() +} + +pub fn main() { + // describe the main window + // + // Window title is set in index.html and window size is ignored on the web, + // so can we leave those off. + let main_window = WindowDesc::new(build_root_widget); + + // create the initial app state + let initial_state = HelloState { + name: "World".into(), + }; + + // start the application + AppLauncher::with_window(main_window) + .launch(initial_state) + .expect("Failed to launch application"); +} + +fn build_root_widget() -> impl Widget { + // a label that will determine its text based on the current app data. + let label = Label::new(|data: &HelloState, _env: &Env| format!("Hello {}!", data.name)); + // a textbox that modifies `name`. + let textbox = TextBox::new() + .with_placeholder("Who are we greeting?") + .fix_width(TEXT_BOX_WIDTH) + .lens(HelloState::name); + + // arrange the two widgets vertically, with some padding + let layout = Flex::column() + .with_child(label) + .with_spacer(VERTICAL_WIDGET_SPACING) + .with_child(textbox); + + // center the two widgets in the available space + Align::centered(layout) +}