init
This commit is contained in:
commit
22f43dbb78
12 changed files with 4314 additions and 0 deletions
1
.envrc
Normal file
1
.envrc
Normal file
|
@ -0,0 +1 @@
|
|||
use flake . --impure
|
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
/target
|
||||
/.direnv
|
||||
/.devenv
|
2
.helix/languages.toml
Normal file
2
.helix/languages.toml
Normal file
|
@ -0,0 +1,2 @@
|
|||
[language-server.rust-analyzer]
|
||||
# config = { cargo = { target = "wasm32-unknown-unknown" } }
|
3737
Cargo.lock
generated
Normal file
3737
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
28
Cargo.toml
Normal file
28
Cargo.toml
Normal file
|
@ -0,0 +1,28 @@
|
|||
[package]
|
||||
name = "mathe-img-processor"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
bytemuck = "1.16.0"
|
||||
eframe = {version = "0.27", features = ["wgpu"]}
|
||||
env_logger = "0.11.3"
|
||||
# winit = { version = "0.30", features = ["rwh_05" ] }
|
||||
# log = "0.4"
|
||||
# console_error_panic_hook = "0.1.6"
|
||||
# console_log = "1.0"
|
||||
# cfg-if = "1"
|
||||
# wgpu = { version = "0.19", features = ["webgl"]}
|
||||
# wasm-bindgen = "0.2"
|
||||
# wasm-bindgen-futures = "0.4.30"
|
||||
# web-sys = { version = "0.3", features = [
|
||||
# "Document",
|
||||
# "Window",
|
||||
# "Element",
|
||||
# ]}
|
||||
|
||||
# [lib]
|
||||
# crate-type = ["cdylib"]
|
||||
|
||||
# [build]
|
||||
# target = "wasm32-unknown-unknown"
|
21
TODO.md
Normal file
21
TODO.md
Normal file
|
@ -0,0 +1,21 @@
|
|||
# TODOs
|
||||
|
||||
- [ ] GPU/Processing/Rendering
|
||||
- [ ] color matrix multiplication
|
||||
- [ ] transform matrix multiplication
|
||||
- [ ] Optional: convolutions
|
||||
- [ ] abstractions over egui stuff
|
||||
- [ ] independent lib/cli app?
|
||||
- [ ] ui
|
||||
- [ ] components
|
||||
- [ ] number picker with slider at bottom?
|
||||
- [ ] (arbitrary size?) matrix component
|
||||
- [ ] matrix list that outputs single matrix multiplied from them all
|
||||
- [ ] Optional: help/what is this buttons
|
||||
- [ ] Optional: explainer per pixel what's happening on hover
|
||||
- [ ] view
|
||||
- [ ] image view
|
||||
- [ ] matrix ("columns"?) to apply
|
||||
- [ ] transform
|
||||
- [ ] color
|
||||
- [ ] Optional: Convolution
|
179
flake.lock
Normal file
179
flake.lock
Normal file
|
@ -0,0 +1,179 @@
|
|||
{
|
||||
"nodes": {
|
||||
"crane": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1715274763,
|
||||
"narHash": "sha256-3Iv1PGHJn9sV3HO4FlOVaaztOxa9uGLfOmUWrH7v7+A=",
|
||||
"owner": "ipetkov",
|
||||
"repo": "crane",
|
||||
"rev": "27025ab71bdca30e7ed0a16c88fd74c5970fc7f5",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "ipetkov",
|
||||
"repo": "crane",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"fenix": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"nixpkgs"
|
||||
],
|
||||
"rust-analyzer-src": "rust-analyzer-src"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1715927173,
|
||||
"narHash": "sha256-2S8hVck6nlyiBifzymDvePl5HWgqvVgxkBZCRax1qD8=",
|
||||
"owner": "nix-community",
|
||||
"repo": "fenix",
|
||||
"rev": "a2d19ef9305841f26c8ab908b1c09a84ca307e18",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-community",
|
||||
"repo": "fenix",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-utils": {
|
||||
"inputs": {
|
||||
"systems": "systems"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1710146030,
|
||||
"narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-utils_2": {
|
||||
"inputs": {
|
||||
"systems": "systems_2"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1689068808,
|
||||
"narHash": "sha256-6ixXo3wt24N/melDWjq70UuHQLxGV8jZvooRanIHXw0=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "919d646de7be200f3bf08cb76ae1f09402b6f9b4",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1715867414,
|
||||
"narHash": "sha256-cu4UEffKkBByyGR6CFs9XP6iSNsKTkq1r66DA5BkYnE=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "bf446f08bff6814b569265bef8374cfdd3d8f0e0",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixpkgs-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"crane": "crane",
|
||||
"fenix": "fenix",
|
||||
"flake-utils": "flake-utils",
|
||||
"nixpkgs": "nixpkgs",
|
||||
"wgsl_analyzer": "wgsl_analyzer"
|
||||
}
|
||||
},
|
||||
"rust-analyzer-src": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1715839492,
|
||||
"narHash": "sha256-EyjtjocGLtB7tqyqwBfadP4y5BBtT5EkoG3kq/zym5U=",
|
||||
"owner": "rust-lang",
|
||||
"repo": "rust-analyzer",
|
||||
"rev": "83ba42043166948db91fcfcfe30e0b7eac10b3d5",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "rust-lang",
|
||||
"ref": "nightly",
|
||||
"repo": "rust-analyzer",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"systems": {
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"systems_2": {
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"wgsl_analyzer": {
|
||||
"inputs": {
|
||||
"crane": [
|
||||
"crane"
|
||||
],
|
||||
"flake-utils": "flake-utils_2",
|
||||
"nixpkgs": [
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1702151450,
|
||||
"narHash": "sha256-bhosTihbW89vkqp1ua0C1HGLJJdCNfRde98z4+IjkOc=",
|
||||
"owner": "wgsl-analyzer",
|
||||
"repo": "wgsl-analyzer",
|
||||
"rev": "8851962fc191aa5ea85a25b0ebd10cb9f70627b3",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "wgsl-analyzer",
|
||||
"repo": "wgsl-analyzer",
|
||||
"type": "github"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
90
flake.nix
Normal file
90
flake.nix
Normal file
|
@ -0,0 +1,90 @@
|
|||
{
|
||||
description = "Build a cargo project without extra checks";
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
|
||||
|
||||
crane = {
|
||||
url = "github:ipetkov/crane";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
fenix = {
|
||||
url = "github:nix-community/fenix";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
|
||||
flake-utils.url = "github:numtide/flake-utils";
|
||||
|
||||
wgsl_analyzer = {
|
||||
url = "github:wgsl-analyzer/wgsl-analyzer";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
inputs.crane.follows = "crane";
|
||||
};
|
||||
};
|
||||
|
||||
outputs = {
|
||||
self,
|
||||
nixpkgs,
|
||||
crane,
|
||||
fenix,
|
||||
flake-utils,
|
||||
wgsl_analyzer,
|
||||
...
|
||||
}:
|
||||
flake-utils.lib.eachDefaultSystem (system: let
|
||||
pkgs = nixpkgs.legacyPackages.${system};
|
||||
|
||||
craneLib = crane.lib.${system};
|
||||
rs-toolchain = with fenix.packages.${system};
|
||||
combine [
|
||||
complete.toolchain
|
||||
targets.wasm32-unknown-unknown.latest.rust-std
|
||||
# rust-analyzer
|
||||
];
|
||||
my-crate = craneLib.buildPackage {
|
||||
src = craneLib.cleanCargoSource (craneLib.path ./.);
|
||||
strictDeps = true;
|
||||
|
||||
buildInputs =
|
||||
[
|
||||
# Add additional build inputs here
|
||||
]
|
||||
++ pkgs.lib.optionals pkgs.stdenv.isDarwin [
|
||||
# Additional darwin specific inputs can be set here
|
||||
pkgs.libiconv
|
||||
];
|
||||
|
||||
# Additional environment variables can be set directly
|
||||
# MY_CUSTOM_VAR = "some value";
|
||||
};
|
||||
in {
|
||||
packages.default = my-crate;
|
||||
|
||||
apps.default = flake-utils.lib.mkApp {
|
||||
drv = my-crate;
|
||||
};
|
||||
|
||||
devShells.default = pkgs.mkShell rec {
|
||||
buildInputs = with pkgs; [
|
||||
pkg-config
|
||||
rs-toolchain
|
||||
udev
|
||||
alsa-lib
|
||||
vulkan-loader
|
||||
libxkbcommon
|
||||
wayland
|
||||
xorg.libX11
|
||||
xorg.libXcursor
|
||||
xorg.libXi
|
||||
xorg.libXrandr # mold-wrapped
|
||||
gtk3
|
||||
clang
|
||||
trunk
|
||||
binaryen
|
||||
wgsl_analyzer.outputs.packages."x86_64-linux".default
|
||||
];
|
||||
|
||||
LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath buildInputs;
|
||||
};
|
||||
});
|
||||
}
|
11
index.html
Normal file
11
index.html
Normal file
|
@ -0,0 +1,11 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=, initial-scale=">
|
||||
<title>Wasm test</title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
</body>
|
||||
</html>
|
39
src/custom3d_wgpu_shader.wgsl
Normal file
39
src/custom3d_wgpu_shader.wgsl
Normal file
|
@ -0,0 +1,39 @@
|
|||
struct VertexOut {
|
||||
@location(0) color: vec4<f32>,
|
||||
@builtin(position) position: vec4<f32>,
|
||||
};
|
||||
|
||||
struct Uniforms {
|
||||
@size(16) angle: f32, // pad to 16 bytes
|
||||
};
|
||||
|
||||
@group(0) @binding(0)
|
||||
var<uniform> uniforms: Uniforms;
|
||||
|
||||
var<private> v_positions: array<vec2<f32>, 3> = array<vec2<f32>, 3>(
|
||||
vec2<f32>(0.0, 1.0),
|
||||
vec2<f32>(1.0, -1.0),
|
||||
vec2<f32>(-1.0, -1.0),
|
||||
);
|
||||
|
||||
var<private> v_colors: array<vec4<f32>, 3> = array<vec4<f32>, 3>(
|
||||
vec4<f32>(1.0, 0.0, 0.0, 1.0),
|
||||
vec4<f32>(0.0, 1.0, 0.0, 1.0),
|
||||
vec4<f32>(0.0, 0.0, 1.0, 1.0),
|
||||
);
|
||||
|
||||
@vertex
|
||||
fn vs_main(@builtin(vertex_index) v_idx: u32) -> VertexOut {
|
||||
var out: VertexOut;
|
||||
|
||||
out.position = vec4<f32>(v_positions[v_idx], 0.0, 1.0);
|
||||
out.position.x = out.position.x * cos(uniforms.angle);
|
||||
out.color = v_colors[v_idx];
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
@fragment
|
||||
fn fs_main(in: VertexOut) -> @location(0) vec4<f32> {
|
||||
return in.color;
|
||||
}
|
190
src/lib.rs
Normal file
190
src/lib.rs
Normal file
|
@ -0,0 +1,190 @@
|
|||
use std::num::NonZeroU64;
|
||||
|
||||
use cb::TriangleRenderResources;
|
||||
use eframe::{
|
||||
egui, egui_wgpu,
|
||||
wgpu::{self, util::DeviceExt},
|
||||
};
|
||||
|
||||
use crate::cb::CustomTriangleCallback;
|
||||
|
||||
pub struct App {
|
||||
name: String,
|
||||
age: u32,
|
||||
}
|
||||
impl App {
|
||||
pub fn new<'a>(cc: &'a eframe::CreationContext<'a>) -> Option<Self> {
|
||||
// Get the WGPU render state from the eframe creation context. This can also be retrieved
|
||||
// from `eframe::Frame` when you don't have a `CreationContext` available.
|
||||
let wgpu_render_state = cc.wgpu_render_state.as_ref()?;
|
||||
|
||||
let device = &wgpu_render_state.device;
|
||||
|
||||
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
|
||||
label: Some("custom3d"),
|
||||
source: wgpu::ShaderSource::Wgsl(include_str!("./custom3d_wgpu_shader.wgsl").into()),
|
||||
});
|
||||
|
||||
let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||
label: Some("custom3d"),
|
||||
entries: &[wgpu::BindGroupLayoutEntry {
|
||||
binding: 0,
|
||||
visibility: wgpu::ShaderStages::VERTEX,
|
||||
ty: wgpu::BindingType::Buffer {
|
||||
ty: wgpu::BufferBindingType::Uniform,
|
||||
has_dynamic_offset: false,
|
||||
min_binding_size: NonZeroU64::new(16),
|
||||
},
|
||||
count: None,
|
||||
}],
|
||||
});
|
||||
|
||||
let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||
label: Some("custom3d"),
|
||||
bind_group_layouts: &[&bind_group_layout],
|
||||
push_constant_ranges: &[],
|
||||
});
|
||||
|
||||
let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||
label: Some("custom3d"),
|
||||
layout: Some(&pipeline_layout),
|
||||
vertex: wgpu::VertexState {
|
||||
module: &shader,
|
||||
entry_point: "vs_main",
|
||||
buffers: &[],
|
||||
// compilation_options: wgpu::PipelineCompilationOptions::default(),
|
||||
},
|
||||
fragment: Some(wgpu::FragmentState {
|
||||
module: &shader,
|
||||
entry_point: "fs_main",
|
||||
targets: &[Some(wgpu_render_state.target_format.into())],
|
||||
// compilation_options: wgpu::PipelineCompilationOptions::default(),
|
||||
}),
|
||||
primitive: wgpu::PrimitiveState::default(),
|
||||
depth_stencil: None,
|
||||
multisample: wgpu::MultisampleState::default(),
|
||||
multiview: None,
|
||||
});
|
||||
|
||||
let uniform_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||
label: Some("custom3d"),
|
||||
contents: bytemuck::cast_slice(&[0.0_f32; 4]), // 16 bytes aligned!
|
||||
// Mapping at creation (as done by the create_buffer_init utility) doesn't require us to to add the MAP_WRITE usage
|
||||
// (this *happens* to workaround this bug )
|
||||
usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::UNIFORM,
|
||||
});
|
||||
|
||||
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
label: Some("custom3d"),
|
||||
layout: &bind_group_layout,
|
||||
entries: &[wgpu::BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: uniform_buffer.as_entire_binding(),
|
||||
}],
|
||||
});
|
||||
|
||||
// Because the graphics pipeline must have the same lifetime as the egui render pass,
|
||||
// instead of storing the pipeline in our `Custom3D` struct, we insert it into the
|
||||
// `paint_callback_resources` type map, which is stored alongside the render pass.
|
||||
wgpu_render_state
|
||||
.renderer
|
||||
.write()
|
||||
.callback_resources
|
||||
.insert(TriangleRenderResources {
|
||||
pipeline,
|
||||
bind_group,
|
||||
uniform_buffer,
|
||||
});
|
||||
|
||||
Some(Self {
|
||||
name: "Jade".to_owned(),
|
||||
age: 19,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl eframe::App for App {
|
||||
fn update(&mut self, ctx: &eframe::egui::Context, frame: &mut eframe::Frame) {
|
||||
egui::CentralPanel::default().show(ctx, |ui| {
|
||||
ui.heading("My egui Application");
|
||||
ui.horizontal(|ui| {
|
||||
let name_label = ui.label("Your name: ");
|
||||
ui.text_edit_singleline(&mut self.name)
|
||||
.labelled_by(name_label.id);
|
||||
});
|
||||
ui.add(egui::Slider::new(&mut self.age, 0..=120).text("age"));
|
||||
if ui.button("Increment").clicked() {
|
||||
self.age += 1;
|
||||
}
|
||||
ui.label(format!("Hello '{}', age {}", self.name, self.age));
|
||||
|
||||
egui::Frame::canvas(ui.style()).show(ui, |ui| {
|
||||
let (rect, response) =
|
||||
ui.allocate_exact_size(egui::Vec2::splat(300.0), egui::Sense::drag());
|
||||
dbg!(response);
|
||||
|
||||
let callback = egui_wgpu::Callback::new_paint_callback(
|
||||
rect,
|
||||
CustomTriangleCallback { angle: 0.5 },
|
||||
);
|
||||
ui.painter().add(callback);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
mod cb {
|
||||
use eframe::{egui, egui_wgpu, wgpu};
|
||||
|
||||
pub struct CustomTriangleCallback {
|
||||
pub angle: f32,
|
||||
}
|
||||
|
||||
impl egui_wgpu::CallbackTrait for CustomTriangleCallback {
|
||||
fn prepare(
|
||||
&self,
|
||||
device: &wgpu::Device,
|
||||
queue: &wgpu::Queue,
|
||||
_screen_descriptor: &egui_wgpu::ScreenDescriptor,
|
||||
_egui_encoder: &mut wgpu::CommandEncoder,
|
||||
resources: &mut egui_wgpu::CallbackResources,
|
||||
) -> Vec<wgpu::CommandBuffer> {
|
||||
let resources: &TriangleRenderResources = resources.get().unwrap();
|
||||
resources.prepare(device, queue, self.angle);
|
||||
Vec::new()
|
||||
}
|
||||
|
||||
fn paint<'a>(
|
||||
&self,
|
||||
_info: egui::PaintCallbackInfo,
|
||||
render_pass: &mut wgpu::RenderPass<'a>,
|
||||
resources: &'a egui_wgpu::CallbackResources,
|
||||
) {
|
||||
let resources: &TriangleRenderResources = resources.get().unwrap();
|
||||
resources.paint(render_pass);
|
||||
}
|
||||
}
|
||||
pub struct TriangleRenderResources {
|
||||
pub pipeline: wgpu::RenderPipeline,
|
||||
pub bind_group: wgpu::BindGroup,
|
||||
pub uniform_buffer: wgpu::Buffer,
|
||||
}
|
||||
|
||||
impl TriangleRenderResources {
|
||||
fn prepare(&self, _device: &wgpu::Device, queue: &wgpu::Queue, angle: f32) {
|
||||
// Update our uniform buffer with the angle from the UI
|
||||
queue.write_buffer(
|
||||
&self.uniform_buffer,
|
||||
0,
|
||||
bytemuck::cast_slice(&[angle, 0.0, 0.0, 0.0]),
|
||||
);
|
||||
}
|
||||
|
||||
fn paint<'rp>(&'rp self, render_pass: &mut wgpu::RenderPass<'rp>) {
|
||||
// Draw our triangle!
|
||||
render_pass.set_pipeline(&self.pipeline);
|
||||
render_pass.set_bind_group(0, &self.bind_group, &[]);
|
||||
render_pass.draw(0..3, 0..1);
|
||||
}
|
||||
}
|
||||
}
|
13
src/main.rs
Normal file
13
src/main.rs
Normal file
|
@ -0,0 +1,13 @@
|
|||
use mathe_img_processor::App;
|
||||
|
||||
fn main() -> Result<(), eframe::Error> {
|
||||
env_logger::init();
|
||||
eframe::run_native(
|
||||
"meow",
|
||||
eframe::NativeOptions {
|
||||
renderer: eframe::Renderer::Wgpu,
|
||||
..Default::default()
|
||||
},
|
||||
Box::new(|cc| Box::new(App::new(cc).unwrap())),
|
||||
)
|
||||
}
|
Loading…
Reference in a new issue