redox-os / orbtk

The Rust UI-Toolkit.
MIT License
3.78k stars 188 forks source link

[Question] Resizing canvas #259

Open uniformbuffer opened 4 years ago

uniformbuffer commented 4 years ago

Hi, i have recently discovered this toolkit, and i like it very much, i was looking for a pure rust gui toolkit from long time. For an application that i'm writing, i'm using the Canvas widget to draw some moving 2D figures. Unfortunaly i have encountered a problem: i'm not able to resize the canvas when window itself resize. Probably i miss something, but if i have understood correctly, to make widget resize with the window, i have to set the alignment to Stretch. The property is also applied to all it's child widgets. Then, when the draw function is called on the Pipeline object, the passed RenderTarget object should have the new width and height, but this not happen, it still have the same size. I tried to set this property on the examples and it worked, but not on the canvas example. Maybe the canvas require some special command to achieve the same result? Thanks for the attention, Have a good day

thivmcthiv commented 4 years ago

If you have the canvas in a grid, and you are using .add() then the grid itself wont resize. Instead you could use .add("auto")

uniformbuffer commented 4 years ago

Hi, thanks for the quick reply. In my project the canvas is not inside a grid, but added directly to a custom widget that is added to the window. It is in a configuration similar to the canvas example inside the repository. If you take the example and simply add .resizeable(true) .v_align(Alignment::Stretch).h_align(Alignment::Stretch) to the main window, the canvas should inherit the property and fill the entire window. but this not happen, only the spin button resize correctly.

Since you mention the grid, i tried to wrap the canvas inside and if i add it using .add("*"), the canvas is added, but won't resize (as expected). If i add it using .add("auto") the canvas is not visible (i have some information printed by the pipeline inside the canvas and it report that during the draw call, the RenderTarget have 0 width and 0 height).

Thanks for the attention, Have a good day

FloVanGH commented 4 years ago

Hi,

I checked the Canvas and I think there is a bug and it is not possible now to resize it on runtime. I could work on a fix next week and let you know.

uniformbuffer commented 4 years ago

Hi, i made some tests last night and i have encountered some stretching problems also with Stack and Container widgets, so maybe there is a correlation with the canvas resize problem. I will provide some code to quickly test what i found:

use orbtk::prelude::*;
fn main() {
    Application::new()
    .window(|ctx| {
        Window::new()
        .title("Test")
        .position((100.0, 100.0))
        .size(300.0, 300.0)
        .resizeable(true)
        .child(
            Stack::new()
            .orientation("horizontal")
            .h_align(Alignment::Stretch)
            .child(Button::new().text("test1").build(ctx))
            .child(Button::new().text("test2").build(ctx))
            .build(ctx)
        )
        .build(ctx)
    })
    .run();
}

The Stack widget do not stretch on the same orientation of the widget itself, so if you set the widget orientation to "horizontal", it will not stretch horizontally, same story for the opposite.

use orbtk::prelude::*;
fn main() {
    Application::new()
    .window(|ctx| {
        Window::new()
        .title("Test")
        .position((100.0, 100.0))
        .size(300.0, 300.0)
        .resizeable(true)
        .child(
            Container::new()
            .h_align(Alignment::Stretch)
            .v_align(Alignment::Stretch)
            .border_width(2.0)
            .border_brush(Brush::from("#FFFFFF"))
            .background(Brush::from("#FFF000"))
            .build(ctx)
        )
        .build(ctx)
    })
    .run();
}

Container instead do not stretch at all in all directions.

I would like to contribute to found the root of the problem, but i have not fully understood how widgets are drawn under the hood (i don't even know what is the responsible component).

Thanks for the attention. Have a good day

FloVanGH commented 4 years ago

Sure with pleasure if you want to help. Each widget has a RenderObject and a LayoutObject. Canvas has a PipelineRenderObject and a DefaultLayout object. You can found both on the api crate. The layouts are processed by the LayoutSystem also in the api crate. The DefaultLayout should be strechable but I'm not sure why it does not work on the Canvas. It stretches initial but not after. I hope that could help you.

uniformbuffer commented 4 years ago

Hi, today I started looking for the bug that causes the widget stretching problem and I focused on the Stack widget. After some debugging it came out that the StackLayout (the layout that is used for the Stack widget) in a specific point overwrite the align property of it's children on the same direction of the orientation of the stack with Alignment::Start. So if you set the orientation of Stack to "horizontal", the "h_align" properties of all it's children will be overwrited to Alignment::Start. Same for the "vertical" stack. This code is on orbtk/crates/api/src/layout/stack.rs at line 146:

            match orientation {
                Orientation::Horizontal => {
                    if let Some(halign) = component_try_mut::<Alignment>(ecm, child, "h_align") {
                        *halign = Alignment::Start;
                    }
                }
                _ => {
                    if let Some(halign) = component_try_mut::<Alignment>(ecm, child, "v_align") {
                        *halign = Alignment::Start;
                    }
                }
            }

Removing this code make the children visible, but there still is a problem: when a container need to calculate it's layout, it ask to all it's children what is the size they desire. If the child have an align different from "stretch", it will have a fixed size, so it will reply it's size as normal. If the child have instead the alignment "stretch", it will reply with the maximum space available (also this is correct). The problem is that the available space requested by a widget with align "stretch" is all the space that the container have, but this space should be shared with the others widget. A widget is not aware if it is in a container with other widgets during the layout calculation. At this point there are multiple ways to handle this situation depending on what is the way the stack is supposed to work. A way to solve this is:

Making the system in this way, all the space that is not used by fixed sized widget will be divided among the dynamic sized widget, using all the available space without waste. If this is the way it it supposed to work, i could try to make a patch, but i'm still understanding the mechanics of the ECS integration (that is a very cool and useful idea), so i don't know if i'm able to apply the modifications without breaking something, maybe someone more skilled could do it.

Anyway, hope this tests help, Have a nice day

FloVanGH commented 4 years ago

Hi, the maybe weird behavior of the StackPanel is intended. It is the same as the StackPanel of WPF and has it purpose. It is a very special layout. For most cases I would suggest to use Grid instead. It is much more flexible and on default if you don't set columns and rows you could use it as alignment layout.