stararawn / kayak_ui Goto Github PK
View Code? Open in Web Editor NEWLicense: Other
License: Other
I'd like to do the following:
let on_event = OnEvent::new(move |_, event| match event.event_type {
EventType::Click(..) => {
writer.send(StartPlacingBuildingEvent);
},
_ => {}
});
where writer
is a bevy EventWriter<T>
.
Currently, when processing events we iterate through every node in the tree using down_iter
:
kayak_ui/kayak_core/src/context.rs
Line 277 in a068b4a
And even with #43, we still iterate through every node:
kayak_ui/kayak_core/src/event_dispatcher.rs
Lines 147 to 149 in 63d62f2
This could be much more efficient if we could smartly recurse down the tree, skipping nodes we don't need to process. Additionally, the only events that actually need to process individual nodes (as of now) are Cursor events. Keyboard and Focus events either happen on a single, known node (the focused node) or are invoked from a Cursor event.
The main issue with performing a smart recursion down the tree for a Cursor event is that we don't always know if a parent's layout contains a descendant's. For example, a child node may overflow the parent's actual bounding box. For self-directed nodes, the parent could even have no width or height since self-directed nodes don't contribute to a parent's layout.
This is an issue because we can't just recursively select the child node that contains the cursor: we don't know if its descendants will contain it or if its actually within another part of the tree entirely.
One possible solution would be to manage a spatial hash of some type, such as a quad-tree, for the nodes. This would cache the layout of every node, allowing fast read-access when determining which nodes contain a specified point. However, it comes at the cost of additional memory overhead. It might also be an issue with animations where a layout could change very quickly in a short period of time, requiring lots of updates to the tree.
Other solutions/suggestions are welcome!
So I'm working on improving the focus management and I came across an idea. However, not one that I felt comfortable just dropping a PR for without opening it up to discussion first (since it could just be a horrendous idea). So here's the problem I found and a possible solution I came up with.
A lot of internal stuff is exposed to the end user that either doesn't need to be or shouldn't be. At best it can be confusing or misleading to users, and at worst it can cause some users to use code in ways they shouldn't (i.e., unintentional hacks that are unstable and could break between versions).
For example, it seems wrong that a user can do this: self.set_id(Index::default())
. And while this might not be that big of an issue ( "users should just not do that" ), what if we want to add other functionality onto widgets in the future? Maybe we add the ability to give widgets their own classes for theming. This might be confusing for the user who thinks that self.add_class("foo")
should work on its own (but might not since the context also needs to be made aware of the change).
Additionally, KayakContext
, has a lot of functionality that doesn't need to be exposed within the context of a widget, but needs to elsewhere (such as the KayakContext::render
and KayakContext::process_events
methods). Methods like these need to be public, but they're dangerous to use within a widget's render method.
So in summary the two main issues are:
KayakContext
methods are also exposed to the user in the render function when they shouldn't beI think the widget system could be restructured in a way that creates a layer of abstraction between the widgets and their context.
Let's assume we have this widget:
#[widget]
fn MyWidget(name: String, fill: Option<Color>) {
// ...
}
To get around the first issue of the widget having too much mutable access to its required widget data, we could separate the render function from the widget itself by placing the actual logic inside an associated method.
This would remove the access to a mutable self
. But we still need access to the actual props. To solve this issue we can generate a new struct, MyWidgetProps
, to contain the prop data. We can then pass this into the render function.
And now we have something that looks a bit more like this:
struct MyWidget {
pub id: Index,
pub children: Children,
pub props: MyWidgetProps,
// ...
}
struct MyWidgetProps {
pub name: String,
pub fill: Option<Color>,
}
impl Widget for MyWidget {
fn render(&mut self, context: &mut KayakContext) {
Self::render(self.props, context);
}
}
impl MyWidget {
fn render(props: MyWidgetProps, context: &mut KayakContext) {
// Generated logic
}
}
Now comes the second issue of direct access to KayakContext
. I think a good way of solving this would be to generate an interface struct. This struct would act as a middleman between the render function and KayakContext
, forwarding only select methods to the context.
There are two ways of going about this.
struct MyWidgetContext<'a> {
context: &'a mut KayakContext,
widget: &'a mut MyWidget
}
struct MyWidgetContext<'a> {
context: &'a mut KayakContext,
widget: &'a mut dyn Widget
}
Though option 1 results in more code being generated, I actually like it better since it allows for greater customization in cases where the widget is written out manually instead of via cookie-cutter macro.
Using this interface struct, we can then replace the direct KayakContext
access:
impl MyWidget {
fn render(mut this: MyWidgetContext) {
// Generated logic
}
}
We can of course call the parameter
context
or something. I'm just being cheeky by calling itthis
here ;)
And of course we'll need some code for it to be useable:
/// We could also create a trait for common methods, but this is probably fine
/// since we'll likely only ever use it internally within a particular widget
/// (in other words, not as a passable trait object)
impl<'a> MyWidgetContext<'a> {
pub fn props(&self) -> &MyWidgetProps {
&self.widget.props
}
pub fn set_styles(&mut self, styles: Option<Style>) {
self.widget.styles = styles;
}
// ...
}
impl MyWidget {
fn render(mut this: MyWidgetContext) {
let MyWidgetProps {name, fill} = this.props();
// Generated logic
}
}
And again, the purpose here is to hide internals so the user isn't confused and doesn't code in a hacky way:
impl MyWidget {
fn render(mut this: MyWidgetContext) {
// It's now impossible to change a widget's ID internally
self.set_id(Index::default());
// You can no longer render-ception
context.render();
// This might seem like a hack to dispatch events but it's an error
context.process_events(vec![InputEvent::MouseLeftPress]);
// Now we can make this properly notify the context
this.add_class("foo");
}
}
MyWidgetProps
MyWidgetContext
to interface between render function and MyWidget
and KayakContext
The biggest alternative, again, is just to trust that the developer isn't going to cause any issues and that we make it clear what code could cause issues. But I think we can still do something to help. And coming up with a solution also improves the overall experience and API.
One big alternative to a portion of this is to just not store data on the widget itself. All relevant data for a widget will be stored in the KayakContext
. This forces all code that needs a bit of data about a widget to also need access to the current context.
This could make code outside the functional component much more cumbersome to use.
Additionally, we run into the issue of storing the widget's ID, since that data does need to exist on the widget regardless.
One of the issues I briefly mentioned was about the idea that updating a widget's own data does not directly alert the context. This could be solved by just having users write the notification code:
#[widget]
fn Foo(context: &mut KayakContext) {
self.add_class("bar");
context.notify(KayakNotify::ClassAdded); // or however it's implemented
}
From an API perspective, this isn't great. Using the Widget Context idea, we can move that required code into the interface method:
impl<'a> FooContext<'a> {
pub fn add_class(&mut self, class: &str) {
self.widget.add_class(class);
context.notify(KayakNotify::ClassAdded);
}
}
#[widget]
fn Foo() {
this.add_class("bar");
}
Also note that we can't do this:
#[widget]
fn Foo(context: &mut KayakContext) {
context.add_class("bar");
}
Since this requires that Foo
have a public method add_class
for the context to set. And the alternatives for those issues go back to the ones listed above.
use crate::OnEvent;
use crate::render_command::RenderCommand;
use crate::styles::{Style, StyleProp};
mod my_widget_module {
use derivative::Derivative;
use crate::{Color, Index, KayakContext, OnEvent, Widget};
use crate::styles::Style;
#[derive(Derivative)]
#[derivative(Default, Debug, PartialEq)]
pub struct MyWidget {
pub id: Index,
pub props: MyWidgetProps,
pub styles: Option<Style>,
#[derivative(Default(value = "None"), Debug = "ignore", PartialEq = "ignore")]
pub on_event: Option<crate::OnEvent>,
#[derivative(Default(value = "None"), Debug = "ignore", PartialEq = "ignore")]
pub children: crate::Children,
}
#[derive(Derivative)]
#[derivative(Default, Debug, PartialEq)]
pub struct MyWidgetProps {
pub name: String,
pub fill: Option<Color>,
}
pub struct MyWidgetContext<'a> {
context: &'a mut KayakContext,
widget: &'a mut MyWidget,
}
impl<'a> MyWidgetContext<'a> {
pub fn set_styles(&mut self, styles: Option<Style>) {
self.widget.styles = styles;
}
pub fn on_event(&mut self, handler: Option<OnEvent>) {
self.widget.on_event = handler;
}
pub fn props(&self) -> &MyWidgetProps {
&self.widget.props
}
}
impl MyWidget {
pub fn generate_context<'a>(&'a mut self, context: &'a mut KayakContext) -> MyWidgetContext<'a> {
MyWidgetContext {
widget: self,
context,
}
}
}
impl Widget for MyWidget {
fn get_id(&self) -> Index {
self.id
}
fn focusable(&self) -> bool {
true
}
fn set_id(&mut self, id: Index) {
self.id = id;
}
fn get_styles(&self) -> Option<Style> {
self.styles.clone()
}
fn get_name(&self) -> String {
String::from("MyWidget")
}
fn on_event(&mut self, context: &mut KayakContext, event: &mut crate::Event) {
let this = self.generate_context(context);
if let Some(on_event) = this.widget.on_event.as_mut() {
if let Ok(mut on_event) = on_event.0.write() {
on_event(this.context, event);
}
}
}
fn render(&mut self, context: &mut KayakContext) {
Self::render(self.generate_context(context));
}
}
}
use my_widget_module::{MyWidget, MyWidgetContext, MyWidgetProps};
// Outside the module so it doesn't have access to the internals
impl MyWidget {
pub fn render(mut this: MyWidgetContext) {
let MyWidgetProps { fill, name } = this.props();
this.set_styles(Some(Style {
render_command: StyleProp::Value(RenderCommand::Empty),
..Default::default()
}));
this.on_event(Some(OnEvent::new(move |_, evt| {
println!("Event fired!");
})));
// ...
}
}
This is all just me thinking of ways to improve on these issues. Are they worth the trouble? And would we stand to gain from implementing a solution like this? I think so, but I could be totally and completely wrong. There could also be other, better solutions than the one I came up with.
Anyways, just wanted to bring this up and see what people thought. So let me know!
Should we keep the current Morphorm layout integration or should we move over to taffy for layouts? I'm hoping the community can help make a decision here. Morphorm hasn't been updated recently but Taffy is being actively developed.
in web development you can have many small styling components that are added together when building a component. (e.g.:
Not sure if theres a way to "add" different styles to form a more complex one in kayak_ui but doing so would be amazing (and would greatly improve code reusability imho).
Such implementation would compare two styles (mut self & style_b) and replace certain fields of struct self
whenever such parameters are "default" and B provides diferent values. The following code does not compile, but the basic logic would be as follows:
impl Add for Style {
type Output = Style;
fn add(self, rhs: Style) -> Style {
let mut resulting_style = Style::default();
for i in Style::default().keys.iter() {
if resulting_style.i == StyleProp::Default {
resulting_style.i = rhs.i;
}
}
resulting_style
}
}
pub fn center_align() -> Style {
Style {
bottom: StyleProp::Value(Units::Stretch(1.0)),
left: StyleProp::Value(Units::Stretch(1.0)),
top: StyleProp::Value(Units::Stretch(1.0)),
right: StyleProp::Value(Units::Stretch(1.0)),
..Default::default()
}
}
pub fn main_background_color() -> Style {
Style {
background_color: StyleProp::Value(Color::BLACK),
..Default::default()
}
}
// this complex style would have bottom, left, top, right & background_color defined. other fields remain default.
pub fn complex_style() -> Style {
center_align() + main_background_color()
}
Not sure if there's already a way to achieve this in kayak_ui. Cheers!
Note: I know it is not possible to iter through struct fields (as opposed to python dicts, for example) but I saw some comments about using serde
to Impl iterable to struct. Maybe this could be an optional feature, since it could add some external dependencies...
This link: https://github.com/StarArawn/kayak_ui/blob/main/kayak_widgets
under topic "Features" in bullet point:
shows a 404 not found page.
Is there a specific problem with using this with bevy 0.7? I've successfully been using it with 0.7 in 2 projects but I want to make sure there's not a pitfall awaiting me
I modified the counter example to add a -1 button like this:
let (count, set_count, ..) = use_state!(0i32);
let on_add = {
let set_count = set_count.clone();
OnEvent::new(move |_, event| {
if event.event_type == EventType::Click {
set_count(count + 1)
}
})
};
let on_sub = OnEvent::new(move |_, event| {
if event.event_type == EventType::Click {
set_count(count - 1)
}
});
rsx! {
<>
<Window position={(50.0, 50.0)} size={(300.0, 300.0)} title={"Counter Example".to_string()}>
<Text styles={Some(text_styles)} size={32.0} content={format!("Current Count: {}", count)}>{}</Text>
<Button on_event={Some(on_add)}>
<Text styles={Some(button_text_styles)} size={24.0} content={"+1".to_string()}>{}</Text>
</Button>
<Button on_event={Some(on_sub)}>
<Text styles={Some(button_text_styles)} size={24.0} content={"-1".to_string()}>{}</Text>
</Button>
</Window>
</>
}
When rendering this, the - symbol is never rendered, but there's a space to the left of the number.
Currently, there's no way to set the opacity of a widget. This is important when creating overlays and other UI effects.
Add an opacity
style property. This property should match the effect of CSS's opacity
. It should:
The W3 specs state that opacities less than 1.0 create a new stacking context. This is because you can't position elements between a non-opaque element and its children (see this example).
I'm not sure how we resolve z-indexes and such so this may or may not be something we need to consider as well. We also may not even need to enforce this if we can properly handle the scenario stated above.
I have an app which uses postprocess, so renders to an image texture. Though, the image texture is not 1:1 with the window size, and instead is scaled down (for a post processing effect).
Spawning a UICameraBundle
with the target set to this image texture causes a fatal panic to occur when the game opens.
2022-08-27T13:33:19.585318Z ERROR wgpu::backend::direct: Handling wgpu errors as fatal by default
thread 'main' panicked at 'wgpu error: Validation Error
Caused by:
In a RenderPass
note: encoder = `<CommandBuffer-(0, 106, Vulkan)>`
In a set_scissor_rect command
Scissor Rect { x: 0, y: 0, w: 1280, h: 720 } is not contained in the render target Extent3d { width: 640, height: 360, depth_or_array_layers: 1 }
', /home/ari/.cargo/git/checkouts/wgpu-a5140d1891ddccc5/a703a78/wgpu/src/backend/direct.rs:2391:5
note: run with `RUST_BACKTRACE=1` environment variable to display a **backtrace**
This issue does not occur with the built-in bevy cameras, but does occur with Kayak UI.
To better maintain style consistency within the codebase, a rustfmt.toml
file should be added to allow automatic formatting of code.
Right now bevy doesn't expose winit's set_cursor_icon
function. We need to enable that functionality in bevy to show different cursors. Then we can hook into the events to change the cursor icon.
I am trying to run the examples (unmodified), but every example results in the attached screenshot.
There is no error message unfortunately, the app seems to run despite not showing anything.
I am running windows 10. So far, I tried the examples "counter", "todo" and "text_box".
Any clue on how to make it work?
Per title we should support Vec
widgets like:
<SomeWidget>
{some_vec.iter().map(|number| {
rsx! {
<Text size={30.0} content={format!("Hello World {}!", number)} />
}
}).collect()}
</SomeWidget>
These should conform to the layout and clip children depending on an overflow style prop.
Currently, when a widget is removed, it leaves behind pretty much all data associated with it. This is because we typically store that data based on its Index
, which can continue to exist after the widget is removed.
There should be a way for this data to be cleaned up. Doing so will also have the added benefit of granting the option of responding to the removal (such as for cleanup functions described in #45) via a Drop
impl (presumably).
So far I can only think of these two suggestions (but there's probably a better way to do it):
Change::Deleted
nodes. Then we just iterate over them and manually remove the necessary data from KayakContext
. This method is probably slower and more cumbersome, but probably much simpler to implement with adequate diffing.weak_table
, to create the data HashMap
and HashSet
fields. Then add a trait method to Widget
allowing it to return a reference to its Index
(or maybe a custom wrapper struct). This way we could use a Weak<Index>
as the key to automatically drop the stored data when the widget is removed. This way is a bit cleaner, but adds a dependency and requires changing a lot more code to integrate it. (Just did some quick testing and I guess widgets are dropped every render? I assume when rebuilding the tree? So maybe this whole idea wouldn't work anyways...)Not sure how to best approach this ๐ค
Piggybacking on my previous issue, I have a state flow setup to go from asset_load -> menu -> ingame. Each with a different UI layout.
If I do not inject a UICameraBundle
at startup, but instead inject this when building says the main_menu UI, it offsets it towards the top left of the screen.
If instead, I inject a UICameraBundle
at startup, then manipulate the RootContext via the enter system of main_menu
pub fn setup_main_menu(
mut commands: Commands,
textures: Res<TextureAssets>,
asset_server: Res<AssetServer>,
mut font_mapping: ResMut<FontMapping>,
mut ctx: Query<(Entity, &mut KayakRootContext)>,
) {
if let Ok((root_entity, mut widget_context)) = ctx.get_single_mut() {
font_mapping.set_default(asset_server.load("fonts/lato/lato-light.kayak_font"));
widget_context.add_plugin(KayakWidgetsContextPlugin);
...
Then the menu is correctly centered within the screen.
In the readme it says 0.7. Is this ready for the latest version?
When using a macos m1 retina screen, or scaled versions of non-retina screens, the origin, xpos, ypos, width, and height of Image
widgets are multiplied by 2. More specifically, the dpi
value here that was recently added increases from 1.0
to 2.0
.
It seems that this dpi value being force-set to 1.0
fixes the issue, but I have no idea if this is a valid fix.
ninepatch widgets are not affected, so I've added one to the image example code to take some screenshots.
use bevy::{
prelude::{App as BevyApp, AssetServer, Commands, Handle, Res, ResMut},
window::WindowDescriptor,
DefaultPlugins,
};
use kayak_core::styles::{PositionType, Edge};
use kayak_ui::{bevy::{BevyContext, BevyKayakUIPlugin, ImageManager, UICameraBundle}, widgets::NinePatch};
use kayak_ui::core::{
render,
styles::{Corner, Style, StyleProp, Units},
Index,
};
use kayak_ui::widgets::{App, Image};
fn startup(
mut commands: Commands,
asset_server: Res<AssetServer>,
mut image_manager: ResMut<ImageManager>,
) {
commands.spawn_bundle(UICameraBundle::new());
let handle: Handle<bevy::render::texture::Image> = asset_server.load("generic-rpg-vendor.png");
let ui_image_handle = image_manager.get(&handle);
let panel_handle: Handle<bevy::render::texture::Image> = asset_server.load("panel.png");
let panel_image_handle = image_manager.get(&panel_handle);
let context = BevyContext::new(|context| {
let image_styles = Style {
position_type: StyleProp::Value(PositionType::SelfDirected),
left: StyleProp::Value(Units::Pixels(100.0)),
top: StyleProp::Value(Units::Pixels(10.0)),
border_radius: StyleProp::Value(Corner::all(500.0)),
width: StyleProp::Value(Units::Pixels(200.0)),
height: StyleProp::Value(Units::Pixels(182.0)),
..Style::default()
};
let nine_patch_styles = Style {
width: StyleProp::Value(Units::Pixels(512.0)),
height: StyleProp::Value(Units::Pixels(512.0)),
..Style::default()
};
render! {
<App>
<NinePatch
styles={Some(nine_patch_styles)}
border={Edge::all(15.0)}
handle={panel_image_handle}
>
</NinePatch>
<Image styles={Some(image_styles)} handle={ui_image_handle} />
</App>
}
});
commands.insert_resource(context);
}
fn main() {
BevyApp::new()
.insert_resource(WindowDescriptor {
width: 1270.0,
height: 720.0,
title: String::from("UI Example"),
..Default::default()
})
.add_plugins(DefaultPlugins)
.add_plugin(BevyKayakUIPlugin)
.add_startup_system(startup)
.run();
}
I try to display 15 Buttons as a grid. The layout works as column or row, but as soon as I change the layout to grid, the app crashes.
My code looks like this:
let items_ui = Style {
layout_type: StyleProp::Value(LayoutType::Grid), // LayoutType::Column works fine
..ui_props.clone().styles.unwrap_or_default()
};
rsx! {
<Element styles={Some(items_ui)}>
{VecTracker::from(handles.iter().map(|(_, h)| {
constructor! {
<Item event_type=
{UIEventType::None}
handle={Some(h.clone())} />
}
}))}
</Element>
}
The error I receive is this:
thread 'main' panicked at 'index out of bounds: the len is 2 but the index is 2', C:\Users\Admin.cargo\git\checkouts\morphorm-b1c9411b3870fca5\1243152\src\layout.rs:1374:37
the code at the described position in layout.rs is this:
let col_start = 2 * node.col_index(store).unwrap_or_default() + 1;
let col_span = 2 * node.col_span(store).unwrap_or(1) - 1;
let col_end = col_start + col_span;
let new_posx = col_widths[col_start].0;
let new_width = col_widths[col_end].0 - new_posx; // this is where the error occurs
let new_posy = row_heights[row_start].0;
let new_height = row_heights[row_end].0 - new_posy;
This looks like a bug to me, but maybe I don't fully understand the layout mechanism.
Any Idea on how to fix the error?
Attempting to add a doc comment on a functional widget doesn't actually pass the doc comment to the generated widget struct. It would make sense if attributes (like doc comments) were also passed to the generated widget.
For example, the following generates a MyWidget
struct without passing along the doc comment:
#[widget]
/// Some doc comment about My Widget
fn MyWidget() {
// ...
}
Sidenote: Passing attributes might also be good for providing additional functionality to a created widget struct, including the usage of custom attributes.
This might be more of a consideration for #56 (if it's even possible, I'm not super proficient with proc macros yet), but it might also be a tangential issue to that RFC, which is why I'm bringing it up here.
Currently diffing is turned off. This is because of the following problem:
A user creates Widgets A > B > C where C is a child of B and B is a child of A.
Widget A state changes and passes new props to C but not to B. Widget A gets marked as dirty and is re-rendered.
When re-rendering the diff for A says to re-render my children. So it re-renders B. B does a diff check and nothing has changed so it will not re-render C even though C has changed.
Always attempt to re-render all children if parent has been marked dirty.
Pros:
Cons:
Diff props to children separately in parent and build a re-render list.
Pros:
Cons:
Others?
We should support tiling for NinePatch images. See unity docs here as a helpful reference:
https://docs.unity3d.com/Manual/9SliceSprites.html
When rendering an image, it is mirrored in the x-axis.
If you replace the nine_patch image example with this image.
you can see that the border is flipped in the y direction (and that the border seems to be stretched.
This also happens with the default nine_patch image example, but it is harder to see.
If you focus on the bordering red/black lines you can see the top-left corner in the origin image is the bottom-left corner in the rendering.
Additionally, the measurements seem to be off. In both cases the Edge::all needs to be doubled to fit the right size of pixels (from 30 to 60 for the new image, and from 15 to 30 for the old), and even then the borders appear stretched.
ex: new image at Edge::all(30.0)
(which should be the right pixel count for the border).
and Edge::all(60.0)
The panel I was expecting looks more like this (it's a kenney asset from the space ui set)
Hi there,
I've been watching this project for a few weeks and notice that the rendering uses bevy which has done a good job at keeping iOS and Android as supported platforms.
Would you be interested in iOS support. I can tell you that some of the examples to this point work in the iOS simulator via:
cargo dinghy --platform auto-ios-aarch64-sim run --example text_box
rm -r target/aarch64-apple-ios-sim/debug/dinghy/text_box/Dinghy.app/resources/
xcrun simctl install booted ./target/aarch64-apple-ios-sim/debug/dinghy/text_box/Dinghy.app/
xcrun simctl launch booted Dinghy
You need to have an iOS simulator booted
of course.
Click/taps don't seem to work as I think those are different winit
events. Also, text input doesn't work because it's pretty specific to iOS.
I am trying to render an ui when I get in a specific state, but for some reason nothing is getting rendered in the window.
And this is my system which should render some texts
I am using this commit version 135e513e8d7f197d04dacfb1c5e8c48db3cb5621
I'm on windows 11, nvidia graphic card and intel processor
Using the hello_world as an example, if I have the following setup:
fn startup(
mut commands: Commands,
mut font_mapping: ResMut<FontMapping>,
asset_server: Res<AssetServer>,
) {
font_mapping.set_default(asset_server.load("roboto.kayak_font"));
let mut widget_context = KayakRootContext::new();
widget_context.add_plugin(KayakWidgetsContextPlugin);
let parent_id = None;
rsx! {
<KayakAppBundle>
<TextWidgetBundle
text={TextProps {
content: "Hello World".into(),
size: 20.0,
..Default::default()
}}
/>
</KayakAppBundle>
}
commands.spawn(UICameraBundle::new(widget_context));
}
fn remove_child(
mut commands: Commands,
keys: Res<Input<KeyCode>>,
q: Query<Entity, With<TextProps>>,
) {
if keys.just_pressed(KeyCode::Space) {
q.iter().for_each(|e| {
commands.entity(e).despawn_recursive();
});
}
}
And if I say remove the TextWidgetBundle
from the KayakApp, it causes a panic. This goes for anything under the RootContext
, if i try to remove KayakApp
it panics as well
This is most notable in the counting example where if you click on the text inside of the count button it increments by 2 instead of by 1.
I think this is a bug with how we propagate events.
Right now we get an event for hovering, but that's not so useful. Instead we should have two events:
MouseIn
and MouseOut
This will allow users to change widgets based on if the mouse is inside of the widget(Hover effects).
Currently the widget state management is very simple. It works but it's a little weird at times. Example of a weird scenario:
let button_id = self.get_id();
let on_event = OnEvent::new(move |context, event| match event.event_type {
EventType::Click => {
dbg!("Clicked!");
context.set_current_id(button_id);
context.set_state::<u16>(blue_button_hover_handle);
}
_ => {
context.set_state::<u16>(blue_button_handle);
}
});
Because OnEvent is set on a child we have to set the current widget id before calling set state here. This isn't obvious as behind the scenes we call set_current_id
This likely will cause issues later on. Instead we should have a better way of handling state associated with a particular widget. One idea might be to have something like:
let widget_state = context.get_widget_state(widget_id);
but we would have to somehow pass this to the closure. We might be able to use an Arc<RwLock<State>>
..
Hello, I've been trying to figure this out all day and figured it would be worth asking here. I'm working on a game in bevy and would like to output a list of inventory items. I have an Inventory struct that contains a HashMap<Enum(ItemType), f32> that houses the inventory's item type and it's respective weight.
I've been struggling getting the VecTracker to work with an Iterator on the HashMap as it yields this rust compile error when trying to do so:
--> src\main.rs:141:5
|
131 | let inventory_items = inventory.get().items;
| --------------- captured outer variable
...
141 | / rsx! {
142 | | <Window position={(1080.0, 0.0)} size={(200.0, 300.0)} title={"Inventory".to_string()}>
143 | | <Element>
144 | | {VecTracker::from(inventory_items.iter().map(|item| {
| | ---------------
| | |
| | variable moved due to use in closure
| | move occurs because `inventory_items` has type `bevy::utils::hashbrown::HashMap<AstroidMaterial, f32>`, which does not implement the `Copy` trait
... |
150 | | </Window>
151 | | }
| | ^
| | |
| |_____captured by this `Fn` closure
| move out of `inventory_items` occurs here
|
= note: this error originates in the macro `rsx` (in Nightly builds, run with -Z macro-backtrace for more info)
Here's the code that results in the above compilation error:
commands.insert_resource(bind(Inventory {items: HashMap::new()}));
let inventory = context.query_world::<Res<Binding<Inventory>>, _, _>(move |inventory| inventory.clone());
context.bind(&inventory);
let inventory_items = inventory.get().items;
rsx! {
<Window position={(1080.0, 0.0)} size={(200.0, 300.0)} title={"Inventory".to_string()}>
<Element>
{VecTracker::from(inventory_items.iter().map(|item| {
constructor! {
<Text content={format!("{:?}", item.clone())} size={16.0} />
}
}))}
</Element>
</Window>
}
Please help with any guidance! I've been hitting my head all day trying to figure this out and I'm pretty sure it's my incomplete understanding of rust that's causing the headache. I appreciate the work being done on this project and any time you take to provide guidance, thanks!
Currently, kayak_ui uses full bevy in each crate, which forces you to build all unecessary features like scene loader, audio, default ui and many more which causes really long builds when kayak_ui is added as dependency in your project.
ERROR wgpu_hal::vulkan::instance: VALIDATION [VUID-VkImageViewCreateInfo-image-04441 (0xb75da543)]
Validation Error: [ VUID-VkImageViewCreateInfo-image-04441 ] Object 0: handle = 0x96fbe2000000005e, type = VK_OBJECT_TYPE_IMAGE; | MessageID = 0xb75da543 | Invalid usage flag for VkImage 0x96fbe2000000005e[] used by vkCreateImageView(). In this case, VkImage should have VK_IMAGE_USAGE_[SAMPLED|STORAGE|COLOR_ATTACHMENT|DEPTH_STENCIL_ATTACHMENT|INPUT_ATTACHMENT|TRANSIENT_ATTACHMENT|SHADING_RATE_IMAGE|FRAGMENT_DENSITY_MAP]_BIT set during creation. The Vulkan spec states: image must have been created with a usage value containing at least one of the usages defined in the valid image usage list for image views (https://vulkan.lunarg.com/doc/view/1.2.189.2/windows/1.2-extensions/vkspec.html#VUID-VkImageViewCreateInfo-image-04441)
I've got a use case where I'd like to render multiple Kayak UI Applications and Mount them in world space. As an example having computer kiosks in the world
I was on the fence about making these two separate pull requests but mounting an App in worldspace \ render texture would almost definitely be something done in addition to mounting a HUD to the UI camera as well.
This would require making BevyContext a component instead of a Resource and providing a system that will call render on each BevyContext. Will also require updating process_events to consume BevyContext components and likely we'll need to create an IndexResource.
Another wrinkle is handling input on WorldSpace UI's. A basic solution could be to use bevy_mod_picking to raycast on click and create a synthetic click event if the clicked object has a BevyContext component.
We can address keyboard inputs to the last clicked UI in worldspace, or if the last click was not on a worldspace UI we can fallback to the UICamera App. We'll probably want to provide the world space UI's some mechanism for automatically yielding an input if camera is out of a certain radius, or possibly a more generic predicate provided by the user
Our initial thought is to have the BevyApp render to a texture and map provide that texture to the User to place within the world as they wish.
Run Bevy example(https://github.com/StarArawn/kayak_ui/blob/main/examples/bevy.rs) in own repo.
2022-01-03T09:34:19.890068Z WARN bevy_asset::asset_server: encountered an error while reading an asset: path not found: /Users/robin/code/bevy/galactic/client/assets/roboto.png
thread 'main' panicked at 'attempt to subtract with overflow', /Users/robin/.cargo/git/checkouts/kayak_ui-299a0b6ac8320811/e25baa9/bevy_kayak_ui/src/render/unified/pipeline.rs:595:25
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
kayak_ui = { git="https://github.com/StarArawn/kayak_ui", rev="e25baa9d0ae0cbb63c07e1f3153af2da5c69b8f4" } (branch update-bevy)
bevy = {git = "https://github.com/bevyengine/bevy.git", commit = "1d0d8a3397bd6fc2c14d42ffd0668d2443748912"}
Spinbox is a number-only TextBox
with a special +
/-
or <
/>
near it. Some examples:
Caves of Qud character creation
They are ubiquitous, and they are needed in various places like character creation and UI elements that usually want small changes based on increment/decrement.
50% of the time? Nothing shows up I think this is because the sub-tree diff fails somehow and so when it merges into the main tree it removes the nodes instead of adding them.
Clipping via the Clip
widget seems to stop working when the window is resized too wide. On my machine it happens when the window is sized with a width of 1387
or higher:
Width: 1386 | Width: 1387 |
---|---|
This happens whether the window is sized via WindowDescriptor
or manually resized. And from my tests, it occurs regardless of window height or widget size (obviously as long as it overflows the Clip
widget).
OS: macOS Big Sur (11.6)
rustc: 1.58.1
Kayak Version: 4b45882 137c421 (pre-release)
Edit: It seems I put the wrong commit for Kayak Version. That commit is after the bevy_kayak_render move which seems to have broken clipping altogether (at least on my machine).
When running the latest version of kayak_ui
on the web, the app will throw an error at startup complaining that the flag BUFFER_BINDINGS_NOT_16_BYTE_ALIGNED
is not supported. Tracking down the docs for wgpu, this flag is simply not supported by webgl
Basically, the structs used in unified_pipeline
shader (shader.wgsl
) aren't laid out in a multiple of 16 bytes. One possible solution would be to pad the structs with unused data to make sure they're a multiple of 16 - I was trying to get this working, but ended up hitting a knowledge gap
More information about alignment & sizes in this Learn Wgpu article and in the W3 spec for WGSL
It seems a bit odd that this wouldn't have come up before, but I'm also quite unfamiliar with wgsl and shaders in general - there may be something else that is causing this
The specific error message I get (Chrom on Ubuntu 22.04):
Right now each extract system(font, image, nine_patch, and quad) each call build_render_primitives
which isn't great for performance.
Instead we should call it once and store it inside of a bevy resource.
Currently a lot of the font stuff is hard coded. To fix this we need to:
I'm not entirely sure if I'm doing something wrong here (still pretty new to rust) but I'm getting a panic when trying to pull an initialized resource out of kayak's BevyContext::query_world. If I get rid of all the kayak_ui stuff, the resource is present as expected.
error
Initial seed: 3485847679
creating ui
getting seed from res
thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: NoSuchResource(NoSuchResource)', ~\
.cargo\git\checkouts\kayak_ui-299a0b6ac8320811\f90c054\kayak_core\src\context.rs:677:71
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
error: process didn't exit successfully: `target\debug\examples\kayak_res_panic.exe` (exit code: 101)
I did my best to try and cut it down to a bare bones reproduction.
cargo.toml
bevy = "0.6.1"
kayak_ui = { git="https://github.com/StarArawn/kayak_ui", rev="f90c0541ea1b8016ccf44904c426ce70e1c9a2aa", features = ["bevy_renderer"] }
rand = "0.8.5"
rand_xoshiro = "0.6.0"
panic.rs
use bevy::prelude::{App as BevyApp, Commands, Res, SystemSet};
use bevy::DefaultPlugins;
use kayak_ui::bevy::BevyKayakUIPlugin;
use kayak_ui::widgets::{App, Text};
use kayak_ui::{
bevy::BevyContext,
core::{render, Index},
};
use rand::Rng;
use rand_xoshiro::{rand_core::SeedableRng, Xoshiro256PlusPlus};
#[derive(Clone, Eq, PartialEq, Debug, Hash)]
enum GameState {
Playing,
}
fn create_ui(mut commands: Commands) {
println!("creating ui");
let context = BevyContext::new(|context| {
println!("getting seed from res");
let seed = context.query_world::<Res<RandomResource>, _, u32>(|c| c.seed);
println!("got seed: {}", seed);
render! {
<App>
<Text content={format!("Seed: {}", seed)} size={12.0} />
</App>
}
});
commands.insert_resource(context);
}
struct RandomResource {
seed: u32,
}
impl Default for RandomResource {
fn default() -> Self {
let mut rng = Xoshiro256PlusPlus::seed_from_u64(1);
let init_seed = rng.gen::<u32>();
println!("Initial seed: {}", init_seed);
RandomResource { seed: init_seed }
}
}
fn main() {
BevyApp::new()
.init_resource::<RandomResource>()
.add_state(GameState::Playing)
.add_plugins(DefaultPlugins)
.add_plugin(BevyKayakUIPlugin)
.add_system_set(SystemSet::on_enter(GameState::Playing).with_system(create_ui))
.run();
}
I tried to make a nearly identical example without kayak that doesn't panic to make sure I wasn't going crazy.
no_panic.rs
use bevy::prelude::*;
use rand::Rng;
use rand_xoshiro::{rand_core::SeedableRng, Xoshiro256PlusPlus};
#[derive(Clone, Eq, PartialEq, Debug, Hash)]
enum GameState {
Playing,
}
fn create_ui(res: Res<RandomResource>) {
println!("getting seed from res");
let seed = res.seed;
println!("got seed: {}", seed);
}
struct RandomResource {
seed: u32,
}
impl Default for RandomResource {
fn default() -> Self {
let mut rng = Xoshiro256PlusPlus::seed_from_u64(1);
let init_seed = rng.gen::<u32>();
println!("Initial seed: {}", init_seed);
RandomResource { seed: init_seed }
}
}
fn main() {
App::new()
.init_resource::<RandomResource>()
.add_state(GameState::Playing)
.add_plugins(DefaultPlugins)
.add_system_set(SystemSet::on_enter(GameState::Playing).with_system(create_ui))
.run();
}
It should be fairly easy to allow for custom cursors instead of using the default OS's cursors.
Adding an optional prop to a widget via Option<T>
sometimes fails (although it works for Strings).
i32
#[widget]
pub fn Button(children: Children, styles: Option<Style>, foo: Option<i32>) { /* ... */ }
And used as:
<Button on_event={Some(on_event)} foo={Some(12)}>
Results in the error:
refutable pattern in local binding: `i32::MIN..=11_i32` and `13_i32..=i32::MAX` not covered
bool
#[widget]
pub fn Button(children: Children, styles: Option<Style>, foo: Option<bool>) { /* ... */ }
<Button on_event={Some(on_event)} foo={Some(false)}>
refutable pattern in local binding: `true` not covered
String
Using an optional String seems to work:
#[widget]
pub fn Button(children: Children, styles: Option<Style>, foo: Option<String>) { /* ... */ }
// Can't do `Some("".to_string())` here
<Button on_event={Some(on_event)} foo={Some(my_string)}>
No error.
On commit: 81fc2a4
When resizing any window the application panics with the following message:
wgpu::backend::direct: Handling wgpu errors as fatal by default
thread 'main' panicked at 'wgpu error: Validation Error
Caused by:
In a RenderPass
note: encoder = `<CommandBuffer-(0, 11974, Vulkan)>`
In a set_scissor_rect command
Invalid ScissorRect parameters
If you have a very basic bevy setup and just add the kayak-ui plugins without having a startup system that injects the KayakRootContext
into bevy resources, it causes a panic.
thread 'main' panicked at 'Resource requested by kayak_ui::widgets::add_widget_systems does not exist: kayak_ui::context::KayakRootContext', /Users/elkxj/.cargo/registry/src/github.com-1ecc6299db9ec823/bevy_ecs-0.9.0/src/system/system_param.rs:520:17
note: run with RUST_BACKTRACE=1
environment variable to display a backtrace
let mut app = App::new();
...
app.add_plugins(DefaultPlugins).add_plugin(KayakContextPlugin).add_plugin(KayakWidgets);
...
app.run();
The image example appears fine on first run
cargo run --example image
but the further the image is from the left or the top, the more stretched it becomes. Almost as if the width of the image is tied to the x coordinate, and the height tied to the y coordinate.
left: StyleProp::Value(Units::Pixels(500.0)),
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.