Closed Yatekii closed 5 years ago
Short answer: no, this isn't possible yet.
Long answer:
The only thing azul is very good at (right now) is drawing SVG lines and shapes (filled and stroked polygons) to a screen and/or loading them asynchronously from a database or file - because that's the main purpose I wrote it for and that part works somewhat.
However, right now there is no layout. Every div is simply stretched to the full window size because I haven't had the time to implement layout.
Theoretically, how you'd implement an input field is rather simple:
fn layout(&self, _: WindowInfo) -> Dom<MyAppData> {
Label::new(&self.text).with_event(On::KeyDown, add_character_to_text).dom()
}
// on key press, update self.text with the received characters
fn add_character_to_text(app_state: AppState<MyAppData>, event: WindowEvent) -> UpdateScreen {
// what keys are currently pressed on the keyboard?
let keys_pressed = app_state.windows[event.window_id].get_keyboard_state().current_keys.iter().cloned();
// append the pressed keys to self.text
app_state.modify(|state| state.text.extend(keys_pressed.into_iter()));
}
At least that's the theory of it, On::KeyDown
doesn't exist yet, this is just the theoretical API. Rendering to a canvas - well, you kind of have to do it via a CPU-backed image, then upload the buffer to the GPU by using AppState::add_image() - this returns an ImageKey
, which you can use to cache the drawn GPU texture, to minimize the traffic between CPU and GPU. Or did you mean just drawing graphical shapes - that would be very simple, the debug example should give you an idea on how to create SvgLayerResource
structs.
For OpenGL drawing, you can simply insert an OpenGL texture into the dom:
fn layout(&self, window: WindowInfo) -> Dom<MyAppData> {
// get the current size of the window
let (window_width, window_height) = window.get_physical_size();
// create a texture that is the size of the window
let tex = window.create_texture(window_width as u32, window_height as u32);
// draw to the texture
tex.as_surface().clear_color(1.0, 1.0, 1.0, 1.0);
// Add the texture to the DOM
Dom::new(NodeType::GlTexture(tex))
}
OpenGL textures are a direct part of the 4 fundamental building blocks of a UI: divs (rectangles), images, text and GlTextures. Every other UI component is merely a composition of these 4 core elements.
But as I said - no layout yet, so the texture will simply stretch to the whole window size. You can draw anything you want to that texture, of course. I used that for the SVG drawing component, for example.
I don't think you'll have much luck with creating an input field, it's simply too early.
Yes, I do use caching internally, but only for the purpose of tracking layout variables. Every DOM node has a unique hash, so I can compare DOM trees for equality if necessary. However, keep in mind that this is Rust, not JavaScript. Rebuilding the DOM is lighting fast, not like in JavaScript. It takes a few microseconds to rebuild the entire DOM, it's simply not a performance bottleneck. In a browser, that's not the case, so people have to build complex virtual DOMs to get the performance they need since browsers weren't built with these abstractions in mind.
Hope that answers your question.
Hey, thanks for the elaborate answer!
It's a real shame layouting does not work yet but it's totally understandable :) I am not sure if I could do this as I am very unfamiliar with the codebase.
As for the input field: Yes, ofc, a rudimentary implementation could work like this, but ofc a useable version requires way more, like a blinking cursor location, a focus marker, copy & paste, etc.
It is absolutely great that GL rendertargets are easy to do, as this is a huuuge pain in GTK.
Best, Yatekii
I've just updated the repostiory to contain an text_input_field.rs
example. Technically it's now possible to create input fields, although this is of course very rudimentary:
So it's technically possible to do this now, but neither ergonomic nor very user-friendly.
As for OpenGL there is a slightly unfinished example here: https://github.com/maps4print/azul/wiki/OpenGL-drawing
Seems azul is not in crates.io? https://crates.io/crates/azul
Yeah, intentionally because it's not very useful yet. Maybe I should strike that from the readme, it seems to confuse people.
Wow those changes are great! maybe I should start using it, so I can provide feedback! All those painful hours wrestling with GTK going to waste ;P I am not sure if 4ms is great for UI rendering tbh. How did you come up with that number anyways? I am pretty sure this depends on the amount of stuff you draw ;) Also for an immediate UI you will kinda have to tesselate a lot, no?
On a different topic: Your SVG rendering, is it intended that you can manipulate the SVG DOM? because I started writing my own SVG renderer, as it seems none of the existing ones can leave the base SVG untouched and can alter it once it's loaded and then store back. resvg for example alters the loaded SVG which is highly undesirable, unless you want to simply render it once.
For 60 fps, your budget is 16 ms. For 144 fps, your budget is 6 ms. So it should be "fast enough". Yes, there are UI libraries that render in half-a-millisecond, however they give up a lot of convenience in order to do so and are mainly game-focused - as I said, this isn't a game-focused library, more a desktop-applications-and-tools-focused library. Ex. you could use this to make the UI for a game engine, but for the game itself, you'd likely want some custom rendering.
How did you come up with that number anyways?
When I started to program azul I was concerned if the performance would be OK, esp. since I am rebuilding the UI every frame. So I asked myself how many DOM nodes a production SPA website has - the answer was ~2000 - 7000, but a lot of those nodes where for web-specific stuff, so let's say maybe 1000 - 3000 nodes. So I simply made a "test renderer" that renders 1000 rectangles in webrender and measured how long the frame time was. So "4ms" is the time for 1000 DOM nodes, for hello-world apps with 10 - 50 DOM nodes, the time is closer to 1ms.
For example, if you want to make a table that spans the whole screen, that's roughly 1000 - 1500 table cells, each one is a separate DOM node. But at the same time, you are covering a lot of screen space - it's unlikely that an application will ever be more complex than 2000 - 3000 nodes maximum. If you have lists / data structures larger than that - simply be smarter and don't render what will be hidden anyways.
And lastly, things like texts are faster than nodes, and cover a lot of screen space - I mean look on your screen, how many rectangles can you spot. Probably not that many, i.e. the text box of this comment is one DOM node, the text itself is one DOM node, the "comment" and "close issue" buttons are two DOM nodes, the tabs in the title bar are one DOM node each - UIs are generally not very complex.
Also for an immediate UI you will kinda have to tesselate a lot, no?
No, I don't think tesselate means what you think it means. Most UIs are just rectangles, made up of exactly two triangles. Generating those takes a few nanoseconds on the CPU, then they get transmitted to the GPU. Tesselation is something like this: http://in2gpu.com/wp-content/uploads/2014/07/tess.png - and that isn't necessary for a quad-based UI, I mean it's always just two triangles making a quad. So there isn't any tesselation going on (esp. since tesselation only works on higher-end GPUs). But that's webrenders part, not mine, webrender also optimizes the clipping / stacking order and manages framebuffers.
If you mean the SVG triangulation (dissecting the polygon into triangles for the GPU - which is done using the lyon library), it depends on where you do it. If you add a SvgShape, the result is cached and only gets triangulated once (when the SVG is loaded). So it's not a huge deal. However, sometimes you might want to draw data dynamically, for example a graph - then it's easier to do the triangulation of the graph every frame and render the resulting triangles every frame. You can do this using the SvgLayerResource::Direct(my_shape)
instead of SvgLayerResource::Reference(id)
. It allows you to structure your SVG shapes into "static" and "dynamic" shapes (i.e. shapes that will never change vs shapes that will). You can also cache the results of your triangulation in your GraphComponent
and be smart about caching - although the documentation on that is a bit nonexistent, it should work just like the two-way data binding. However - only do so once it becomes an actual performance problem. It's likely that the re-triangulation is faster than you think and you're prematurely optimizing.
However, if you compare this to solutions that render everything on the CPU, it's still faster of course. Last week I had to use Inkscape to make the diagrams for the wiki - on Windows, Inkscape is CPU-rendered as far as I know and man, it was just so slow, even just with a few texts and shapes. So I do think that this solution is faster than a CPU-based pixel-per-pixel drawing.
Your SVG rendering, is it intended that you can manipulate the SVG DOM?
No. Azul provides a default SVG loader using usvg. I'm not aware that it "manipulates" the DOM, it just loads the SVG dom from a file. You can parse and load SVG with own library if you don't like that and disable usvg - just look in the svg.rs file how the svg_to_lyon module to see how it maps from usvg -> lyon. Maybe I need to re-export some types from lyon, tell me if you need some types re-exported.
For manipulation or saving SVG - no, you have to use external libraries or write your own. Azul is just for rendering. If you want to write your own SVG renderer, you should be using OpenGL textures instead of the SVG module though. Look for the OpenGL tutorial on how to render your own textures.
Hope that answers your questions. The library is still not very usable, but a few bugs have been fixed since last time. Currently I'm working on getting the spreadsheet demo to render correctly, but not on the master branch, so that's why you don't see any updates in the last few days.
For 60 fps, your budget is 16 ms. For 144 fps, your budget is 6 ms. So it should be "fast enough". Yes, there are UI libraries that render in half-a-millisecond,
No worries, I didn't mean that 4ms is too slow. I just meant, that I don't know whether this is fast compared to other libs or not. Also, yes ofc. my budget is tight, but as you don't really redraw every frame but only when the "VDom" changes, I guess that's ok. Every application will take a dip in FPS once in a while (at least if they have CPU heavy stuff). Still, if you want to render at 144Hz, 4ms is very much. We'll see how it pans out =)
So I asked myself how many DOM nodes a production SPA website has - the answer was ~2000 - 7000
That was the important bit I was missing where I read the ~4ms, because where I found that number there was no more info. SO that number alone was worthless ;)
Of course you will have to make smart nodes like ListViews and the like. But that's the case with any library that's really performant :) I am comming from WPF (the best till this date) and Mithril.js (tried React and Vue too but stuck with Mithril), so I am aware of the mechanics :) I was just wondering where you had the numbers from ;)
I am well aware what tesselation means, I just didn't think far enough and realized it's really only rectangles. But for example a border, you will at least require 8 Vertices ;) (well you can to clipping etc. too but it will be pain and you will be overdrawing a lot). I don't know how they do rounded corners, but I guess they use something like clipping masks.
Thanks for the info about the SVG part. The reason I ask this is, that I want to be able to load any SVG and animate/modify it. For one part I want to do simple 2D rendering of animations and for the other part, I need it for my project https://github.com/Yatekii/copper (hope link is okay, I can remove it too), a PCB-EDA, where I want to store components actually as SVGs (because why reinvent the wheel?). I have a base design worked out and will publish it on GH once it's running, maybe you can give your take too then.
Hope that answers your questions. The library is still not very usable, but a few bugs have been fixed since last time.
That's no issue at all. I have felt it with my own projects besides work ... such a slow progress. If you have something you are glad to have tested/fixed, hit me with it. I wanna tinker with azul anyways and I am more than glad to fix/do something for the lib. I am just not sure how fast/easy that will go as I don't know any internas of the lib ;)
All I gotta say is: Great job mate!
P.S: Do you have some kind of presence on IRC or the likes (rather not web-based ;))? Would be awesome to discuss in a more convenient fashion than GH issues that then get offtopic pretty fast ....
I've set up a discord server - https://discord.gg/nxUmsCG (I know, hipster and web-based, but making a custom server was very easy, so eh...). It's mostly to just keep people in one place - I could open an IRC channel and the do an IRC / discord bridge - post 0.1 I was thinking about a proper forum, but that's going to need hosting space and setup and so on... I didn't want to start an empty forum, you know?
The 4ms included rendering of the entire screen (like, real rendering). The thing about VDom-diffing is that it can be more expensive to create a diff than it is to just brute-force render the DOM again. Right now, DOM diffing is implemented (sort-of), but not active (it's a post-0.1 goal, since it only improves performance). However, my target was 60fps and well, 4ms is well below the target of 16ms. There are game-oriented UIs that can render in 0.5ms, that would of course be the dream goal, but that's a pretty high bar because azul does a lot of "convenience" stuff that these libraries don't.
Links are fine, I'm always keen to check out what other people have done. I was thinking about a port of openboardview, but then I just thought that it would be too much work.
As for storing PCBs as SVGs... I don't know if that's going to be a good idea. Once you need custom metadata or something like that, you'll maybe hit the limits of SVG pretty quickly. But what do I know, maybe it'll work out. Cheers.
Okay I'm closing this, mostly since the questions are answered and the features are implemented now.
So I'm not seeing any reason to keep this issue open, if necessary I can also give advice on Discord or just open a new issue. Thanks for asking.
Hi!
I have eagerly been following your project. It looks very promising.
I am looking into using azul for my project where I use my own 2D renderer for the main canvas. Atm I use relm with gtk-rs. but GTK is really suboptimal, especially when it comes to rendering to an OGL canvas.
Now, I am well aware that azul is in a pretty early stage, but I am very willing to take all the bugs and maybe even fix them if the base features work.
So what I need is basic text-input like
<input>
in HTML and rendering to a canvas. How well is this doable atm? I saw the explanations concerning my own 2D rendering in the README.md but that code doesn't seem to do anything with OGL at all.Can you tell me if those two features work in a base form and maybe point me to it?
Btw, do you use caching like a vdom internally? Or do you just calculate the entire UI each frame?
Best, Yatekii