Browse Source

Add a custom index.html for the viewport stuff

Jonathan Kelley 2 years ago
parent
commit
2ad85c9b28

+ 12 - 0
examples/ios_demo/src/index.html

@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>Dioxus app</title>
+    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
+    <!-- CUSTOM HEAD -->
+  </head>
+  <body>
+    <div id="main"></div>
+    <!-- MODULE LOADER -->
+  </body>
+</html>

+ 33 - 210
examples/ios_demo/src/lib.rs

@@ -1,225 +1,44 @@
 use anyhow::Result;
 use anyhow::Result;
 use dioxus::prelude::*;
 use dioxus::prelude::*;
-use dioxus_elements::input_data::keyboard_types::Key;
-#[cfg(target_os = "android")]
-use wry::android_binding;
 
 
 pub fn main() -> Result<()> {
 pub fn main() -> Result<()> {
     init_logging();
     init_logging();
 
 
-    dioxus_desktop::launch(app);
+    // Right now we're going through dioxus-desktop but we'd like to go through dioxus-mobile
+    // That will seed the index.html with some fixes that prevent the page from scrolling/zooming etc
+    dioxus_desktop::launch_cfg(
+        app,
+        // Note that we have to disable the viewport goofiness of the browser.
+        // Dioxus_mobile should do this for us
+        Config::default().with_custom_index(include_str!("index.html").to_string()),
+    );
+
     Ok(())
     Ok(())
 }
 }
 
 
 fn app(cx: Scope) -> Element {
 fn app(cx: Scope) -> Element {
-    cx.render(rsx! {
-        div { "Hello World!" }
-    })
+    let items = use_state(cx, || vec![1, 2, 3]);
+
+    render! {
+        div {
+            h1 { "Hello, Mobile"}
+            div { margin_left: "auto", margin_right: "auto", width: "200px", padding: "10px", border: "1px solid black",
+                button {
+                    onclick: move|_| {
+                        let mut _items = items.make_mut();
+                        let len = _items.len() + 1;
+                        _items.push(len);
+                    },
+                    "Add item"
+                }
+                for item in items.iter() {
+                    div { "- {item}" }
+                }
+            }
+        }
+    }
 }
 }
 
 
-// #[derive(PartialEq, Eq, Clone, Copy)]
-// pub enum FilterState {
-//     All,
-//     Active,
-//     Completed,
-// }
-
-// #[derive(Debug, PartialEq, Eq, Clone)]
-// pub struct TodoItem {
-//     pub id: u32,
-//     pub checked: bool,
-//     pub contents: String,
-// }
-
-// pub fn app(cx: Scope<()>) -> Element {
-//     let todos = use_state(cx, im_rc::HashMap::<u32, TodoItem>::default);
-//     let filter = use_state(cx, || FilterState::All);
-//     let draft = use_state(cx, || "".to_string());
-//     let todo_id = use_state(cx, || 0);
-
-//     // Filter the todos based on the filter state
-//     let mut filtered_todos = todos
-//         .iter()
-//         .filter(|(_, item)| match **filter {
-//             FilterState::All => true,
-//             FilterState::Active => !item.checked,
-//             FilterState::Completed => item.checked,
-//         })
-//         .map(|f| *f.0)
-//         .collect::<Vec<_>>();
-//     filtered_todos.sort_unstable();
-
-//     let active_todo_count = todos.values().filter(|item| !item.checked).count();
-//     let active_todo_text = match active_todo_count {
-//         1 => "item",
-//         _ => "items",
-//     };
-
-//     let show_clear_completed = todos.values().any(|todo| todo.checked);
-
-//     let selected = |state| {
-//         if *filter == state {
-//             "selected"
-//         } else {
-//             "false"
-//         }
-//     };
-
-//     cx.render(rsx! {
-//         section { class: "todoapp",
-//             style { include_str!("./style.css") }
-//             header { class: "header",
-//                 h1 {"todos"}
-//                 input {
-//                     class: "new-todo",
-//                     placeholder: "What needs to be done?",
-//                     value: "{draft}",
-//                     autofocus: "true",
-//                     oninput: move |evt| {
-//                         draft.set(evt.value.clone());
-//                     },
-//                     onkeydown: move |evt| {
-//                         if evt.key() == Key::Enter && !draft.is_empty() {
-//                             todos.make_mut().insert(
-//                                 **todo_id,
-//                                 TodoItem {
-//                                     id: **todo_id,
-//                                     checked: false,
-//                                     contents: draft.to_string(),
-//                                 },
-//                             );
-//                             *todo_id.make_mut() += 1;
-//                             draft.set("".to_string());
-//                         }
-//                     }
-//                 }
-//             }
-//             section {
-//                 class: "main",
-//                 if !todos.is_empty() {
-//                     rsx! {
-//                         input {
-//                             id: "toggle-all",
-//                             class: "toggle-all",
-//                             r#type: "checkbox",
-//                             onchange: move |_| {
-//                                 let check = active_todo_count != 0;
-//                                 for (_, item) in todos.make_mut().iter_mut() {
-//                                     item.checked = check;
-//                                 }
-//                             },
-//                             checked: if active_todo_count == 0 { "true" } else { "false" },
-//                         }
-//                         label { r#for: "toggle-all" }
-//                     }
-//                 }
-//                 ul { class: "todo-list",
-//                     filtered_todos.iter().map(|id| rsx!(TodoEntry {
-//                         key: "{id}",
-//                         id: *id,
-//                         todos: todos,
-//                     }))
-//                 }
-//                 (!todos.is_empty()).then(|| rsx!(
-//                     footer { class: "footer",
-//                         span { class: "todo-count",
-//                             strong {"{active_todo_count} "}
-//                             span {"{active_todo_text} left"}
-//                         }
-//                         ul { class: "filters",
-//                             for (state, state_text, url) in [
-//                                 (FilterState::All, "All", "#/"),
-//                                 (FilterState::Active, "Active", "#/active"),
-//                                 (FilterState::Completed, "Completed", "#/completed"),
-//                             ] {
-//                                 li {
-//                                     a {
-//                                         href: url,
-//                                         class: selected(state),
-//                                         onclick: move |_| filter.set(state),
-//                                         prevent_default: "onclick",
-//                                         state_text
-//                                     }
-//                                 }
-//                             }
-//                         }
-//                         show_clear_completed.then(|| rsx!(
-//                             button {
-//                                 class: "clear-completed",
-//                                 onclick: move |_| todos.make_mut().retain(|_, todo| !todo.checked),
-//                                 "Clear completed"
-//                             }
-//                         ))
-//                     }
-//                 ))
-//             }
-//         }
-//         footer { class: "info",
-//             p { "Double-click to edit a todo" }
-//             p { "Created by ", a { href: "http://github.com/jkelleyrtp/", "jkelleyrtp" }}
-//             p { "Part of ", a { href: "http://todomvc.com", "TodoMVC" }}
-//         }
-//     })
-// }
-
-// #[derive(Props)]
-// pub struct TodoEntryProps<'a> {
-//     todos: &'a UseState<im_rc::HashMap<u32, TodoItem>>,
-//     id: u32,
-// }
-
-// pub fn TodoEntry<'a>(cx: Scope<'a, TodoEntryProps<'a>>) -> Element {
-//     let is_editing = use_state(cx, || false);
-
-//     let todos = cx.props.todos.get();
-//     let todo = &todos[&cx.props.id];
-//     let completed = if todo.checked { "completed" } else { "" };
-//     let editing = if **is_editing { "editing" } else { "" };
-
-//     cx.render(rsx!{
-//         li {
-//             class: "{completed} {editing}",
-//             div { class: "view",
-//                 input {
-//                     class: "toggle",
-//                     r#type: "checkbox",
-//                     id: "cbg-{todo.id}",
-//                     checked: "{todo.checked}",
-//                     oninput: move |evt| {
-//                         cx.props.todos.make_mut()[&cx.props.id].checked = evt.value.parse().unwrap();
-//                     }
-//                 }
-//                 label {
-//                     r#for: "cbg-{todo.id}",
-//                     ondblclick: move |_| is_editing.set(true),
-//                     prevent_default: "onclick",
-//                     "{todo.contents}"
-//                 }
-//                 button {
-//                     class: "destroy",
-//                     onclick: move |_| { cx.props.todos.make_mut().remove(&todo.id); },
-//                     prevent_default: "onclick",
-//                 }
-//             }
-//             is_editing.then(|| rsx!{
-//                 input {
-//                     class: "edit",
-//                     value: "{todo.contents}",
-//                     oninput: move |evt| cx.props.todos.make_mut()[&cx.props.id].contents = evt.value.clone(),
-//                     autofocus: "true",
-//                     onfocusout: move |_| is_editing.set(false),
-//                     onkeydown: move |evt| {
-//                         match evt.key() {
-//                             Key::Enter | Key::Escape | Key::Tab => is_editing.set(false),
-//                             _ => {}
-//                         }
-//                     },
-//                 }
-//             })
-//         }
-//     })
-// }
-
 #[cfg(target_os = "android")]
 #[cfg(target_os = "android")]
 fn init_logging() {
 fn init_logging() {
     android_logger::init_once(
     android_logger::init_once(
@@ -250,6 +69,10 @@ fn _start_app() {
     main().unwrap();
     main().unwrap();
 }
 }
 
 
+use dioxus_desktop::Config;
+#[cfg(target_os = "android")]
+use wry::android_binding;
+
 #[no_mangle]
 #[no_mangle]
 #[inline(never)]
 #[inline(never)]
 #[cfg(any(target_os = "android", target_os = "ios"))]
 #[cfg(any(target_os = "android", target_os = "ios"))]

+ 0 - 379
examples/ios_demo/src/style.css

@@ -1,379 +0,0 @@
-html,
-body {
-    margin: 0;
-    padding: 0;
-}
-
-button {
-    margin: 0;
-    padding: 0;
-    border: 0;
-    background: none;
-    font-size: 100%;
-    vertical-align: baseline;
-    font-family: inherit;
-    font-weight: inherit;
-    color: inherit;
-    -webkit-appearance: none;
-    appearance: none;
-    -webkit-font-smoothing: antialiased;
-    -moz-osx-font-smoothing: grayscale;
-}
-
-body {
-    font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif;
-    line-height: 1.4em;
-    background: #f5f5f5;
-    color: #4d4d4d;
-    min-width: 230px;
-    max-width: 550px;
-    margin: 0 auto;
-    -webkit-font-smoothing: antialiased;
-    -moz-osx-font-smoothing: grayscale;
-    font-weight: 300;
-}
-
-:focus {
-    outline: 0;
-}
-
-.hidden {
-    display: none;
-}
-
-.todoapp {
-    background: #fff;
-    margin: 130px 0 40px 0;
-    position: relative;
-    box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2),
-        0 25px 50px 0 rgba(0, 0, 0, 0.1);
-}
-
-.todoapp input::-webkit-input-placeholder {
-    font-style: italic;
-    font-weight: 300;
-    color: #e6e6e6;
-}
-
-.todoapp input::-moz-placeholder {
-    font-style: italic;
-    font-weight: 300;
-    color: #e6e6e6;
-}
-
-.todoapp input::input-placeholder {
-    font-style: italic;
-    font-weight: 300;
-    color: #e6e6e6;
-}
-
-.todoapp h1 {
-    position: absolute;
-    top: -155px;
-    width: 100%;
-    font-size: 100px;
-    font-weight: 100;
-    text-align: center;
-    color: rgba(175, 47, 47, 0.15);
-    -webkit-text-rendering: optimizeLegibility;
-    -moz-text-rendering: optimizeLegibility;
-    text-rendering: optimizeLegibility;
-}
-
-.new-todo,
-.edit {
-    position: relative;
-    margin: 0;
-    width: 100%;
-    font-size: 24px;
-    font-family: inherit;
-    font-weight: inherit;
-    line-height: 1.4em;
-    border: 0;
-    color: inherit;
-    padding: 6px;
-    border: 1px solid #999;
-    box-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2);
-    box-sizing: border-box;
-    -webkit-font-smoothing: antialiased;
-    -moz-osx-font-smoothing: grayscale;
-}
-
-.new-todo {
-    padding: 16px 16px 16px 60px;
-    border: none;
-    background: rgba(0, 0, 0, 0.003);
-    box-shadow: inset 0 -2px 1px rgba(0, 0, 0, 0.03);
-}
-
-.main {
-    position: relative;
-    z-index: 2;
-    border-top: 1px solid #e6e6e6;
-}
-
-.toggle-all {
-    text-align: center;
-    border: none;
-    /* Mobile Safari */
-    opacity: 0;
-    position: absolute;
-}
-
-.toggle-all+label {
-    width: 60px;
-    height: 34px;
-    font-size: 0;
-    position: absolute;
-    top: -52px;
-    left: -13px;
-    -webkit-transform: rotate(90deg);
-    transform: rotate(90deg);
-}
-
-.toggle-all+label:before {
-    content: '❯';
-    font-size: 22px;
-    color: #e6e6e6;
-    padding: 10px 27px 10px 27px;
-}
-
-.toggle-all:checked+label:before {
-    color: #737373;
-}
-
-.todo-list {
-    margin: 0;
-    padding: 0;
-    list-style: none;
-}
-
-.todo-list li {
-    position: relative;
-    font-size: 24px;
-    border-bottom: 1px solid #ededed;
-}
-
-.todo-list li:last-child {
-    border-bottom: none;
-}
-
-.todo-list li.editing {
-    border-bottom: none;
-    padding: 0;
-}
-
-.todo-list li.editing .edit {
-    display: block;
-    width: 506px;
-    padding: 12px 16px;
-    margin: 0 0 0 43px;
-}
-
-.todo-list li.editing .view {
-    display: none;
-}
-
-.todo-list li .toggle {
-    text-align: center;
-    width: 40px;
-    /* auto, since non-WebKit browsers doesn't support input styling */
-    height: auto;
-    position: absolute;
-    top: 0;
-    bottom: 0;
-    margin: auto 0;
-    border: none;
-    /* Mobile Safari */
-    -webkit-appearance: none;
-    appearance: none;
-}
-
-.todo-list li .toggle {
-    opacity: 0;
-}
-
-.todo-list li .toggle+label {
-    /*
-		Firefox requires `#` to be escaped - https://bugzilla.mozilla.org/show_bug.cgi?id=922433
-		IE and Edge requires *everything* to be escaped to render, so we do that instead of just the `#` - https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/7157459/
-	*/
-    background-image: url('data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%2240%22%20height%3D%2240%22%20viewBox%3D%22-10%20-18%20100%20135%22%3E%3Ccircle%20cx%3D%2250%22%20cy%3D%2250%22%20r%3D%2250%22%20fill%3D%22none%22%20stroke%3D%22%23ededed%22%20stroke-width%3D%223%22/%3E%3C/svg%3E');
-    background-repeat: no-repeat;
-    background-position: center left;
-}
-
-.todo-list li .toggle:checked+label {
-    background-image: url('data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%2240%22%20height%3D%2240%22%20viewBox%3D%22-10%20-18%20100%20135%22%3E%3Ccircle%20cx%3D%2250%22%20cy%3D%2250%22%20r%3D%2250%22%20fill%3D%22none%22%20stroke%3D%22%23bddad5%22%20stroke-width%3D%223%22/%3E%3Cpath%20fill%3D%22%235dc2af%22%20d%3D%22M72%2025L42%2071%2027%2056l-4%204%2020%2020%2034-52z%22/%3E%3C/svg%3E');
-}
-
-.todo-list li label {
-    word-break: break-all;
-    padding: 15px 15px 15px 60px;
-    display: block;
-    line-height: 1.2;
-    transition: color 0.4s;
-}
-
-.todo-list li.completed label {
-    color: #d9d9d9;
-    text-decoration: line-through;
-}
-
-.todo-list li .destroy {
-    display: none;
-    position: absolute;
-    top: 0;
-    right: 10px;
-    bottom: 0;
-    width: 40px;
-    height: 40px;
-    margin: auto 0;
-    font-size: 30px;
-    color: #cc9a9a;
-    margin-bottom: 11px;
-    transition: color 0.2s ease-out;
-}
-
-.todo-list li .destroy:hover {
-    color: #af5b5e;
-}
-
-.todo-list li .destroy:after {
-    content: '×';
-}
-
-.todo-list li:hover .destroy {
-    display: block;
-}
-
-.todo-list li .edit {
-    display: none;
-}
-
-.todo-list li.editing:last-child {
-    margin-bottom: -1px;
-}
-
-.footer {
-    color: #777;
-    padding: 10px 15px;
-    height: 20px;
-    text-align: center;
-    border-top: 1px solid #e6e6e6;
-}
-
-.footer:before {
-    content: '';
-    position: absolute;
-    right: 0;
-    bottom: 0;
-    left: 0;
-    height: 50px;
-    overflow: hidden;
-    box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2),
-        0 8px 0 -3px #f6f6f6,
-        0 9px 1px -3px rgba(0, 0, 0, 0.2),
-        0 16px 0 -6px #f6f6f6,
-        0 17px 2px -6px rgba(0, 0, 0, 0.2);
-}
-
-.todo-count {
-    float: left;
-    text-align: left;
-}
-
-.todo-count strong {
-    font-weight: 300;
-}
-
-.filters {
-    margin: 0;
-    padding: 0;
-    list-style: none;
-    position: absolute;
-    right: 0;
-    left: 0;
-}
-
-.filters li {
-    display: inline;
-}
-
-.filters li a {
-    color: inherit;
-    margin: 3px;
-    padding: 3px 7px;
-    text-decoration: none;
-    border: 1px solid transparent;
-    border-radius: 3px;
-}
-
-.filters li a:hover {
-    border-color: rgba(175, 47, 47, 0.1);
-}
-
-.filters li a.selected {
-    border-color: rgba(175, 47, 47, 0.2);
-}
-
-.clear-completed,
-html .clear-completed:active {
-    float: right;
-    position: relative;
-    line-height: 20px;
-    text-decoration: none;
-    cursor: pointer;
-}
-
-.clear-completed:hover {
-    text-decoration: underline;
-}
-
-.info {
-    margin: 65px auto 0;
-    color: #bfbfbf;
-    font-size: 10px;
-    text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
-    text-align: center;
-}
-
-.info p {
-    line-height: 1;
-}
-
-.info a {
-    color: inherit;
-    text-decoration: none;
-    font-weight: 400;
-}
-
-.info a:hover {
-    text-decoration: underline;
-}
-
-/*
-	Hack to remove background from Mobile Safari.
-	Can't use it globally since it destroys checkboxes in Firefox
-*/
-@media screen and (-webkit-min-device-pixel-ratio:0) {
-
-    .toggle-all,
-    .todo-list li .toggle {
-        background: none;
-    }
-
-    .todo-list li .toggle {
-        height: 40px;
-    }
-}
-
-@media (max-width: 430px) {
-    .footer {
-        height: 50px;
-    }
-
-    .filters {
-        bottom: 10px;
-    }
-}

+ 26 - 14
packages/desktop/src/lib.rs

@@ -34,6 +34,7 @@ pub use eval::{use_eval, EvalResult};
 use futures_util::{pin_mut, FutureExt};
 use futures_util::{pin_mut, FutureExt};
 use shortcut::ShortcutRegistry;
 use shortcut::ShortcutRegistry;
 pub use shortcut::{use_global_shortcut, ShortcutHandle, ShortcutId, ShortcutRegistryError};
 pub use shortcut::{use_global_shortcut, ShortcutHandle, ShortcutId, ShortcutRegistryError};
+use std::cell::Cell;
 use std::rc::Rc;
 use std::rc::Rc;
 use std::task::Waker;
 use std::task::Waker;
 use std::{collections::HashMap, sync::Arc};
 use std::{collections::HashMap, sync::Arc};
@@ -154,18 +155,10 @@ pub fn launch_with_props<P: 'static>(root: Component<P>, props: P, cfg: Config)
 
 
     let shortcut_manager = ShortcutRegistry::new(&event_loop);
     let shortcut_manager = ShortcutRegistry::new(&event_loop);
 
 
-    let web_view = create_new_window(
-        cfg,
-        &event_loop,
-        &proxy,
-        VirtualDom::new_with_props(root, props),
-        &queue,
-        &event_handlers,
-        shortcut_manager.clone(),
-    );
-
-    // By default, we'll create a new window when the app starts
-    queue.borrow_mut().push(web_view);
+    // move the props into a cell so we can pop it out later to create the first window
+    // iOS panics if we create a window before the event loop is started
+    let props = Rc::new(Cell::new(Some(props)));
+    let cfg = Rc::new(Cell::new(Some(cfg)));
 
 
     event_loop.run(move |window_event, event_loop, control_flow| {
     event_loop.run(move |window_event, event_loop, control_flow| {
         *control_flow = ControlFlow::Wait;
         *control_flow = ControlFlow::Wait;
@@ -193,8 +186,27 @@ pub fn launch_with_props<P: 'static>(root: Component<P>, props: P, cfg: Config)
                 _ => {}
                 _ => {}
             },
             },
 
 
-            Event::NewEvents(StartCause::Init)
-            | Event::UserEvent(UserWindowEvent(EventData::NewWindow, _)) => {
+            Event::NewEvents(StartCause::Init) => {
+                //
+                let props = props.take().unwrap();
+                let cfg = cfg.take().unwrap();
+
+                let handler = create_new_window(
+                    cfg,
+                    &event_loop,
+                    &proxy,
+                    VirtualDom::new_with_props(root, props),
+                    &queue,
+                    &event_handlers,
+                    shortcut_manager.clone(),
+                );
+
+                let id = handler.desktop_context.webview.window().id();
+                webviews.insert(id, handler);
+                _ = proxy.send_event(UserWindowEvent(EventData::Poll, id));
+            }
+
+            Event::UserEvent(UserWindowEvent(EventData::NewWindow, _)) => {
                 for handler in queue.borrow_mut().drain(..) {
                 for handler in queue.borrow_mut().drain(..) {
                     let id = handler.desktop_context.webview.window().id();
                     let id = handler.desktop_context.webview.window().id();
                     webviews.insert(id, handler);
                     webviews.insert(id, handler);