From 46476c5515f3e97343914567fb5760692e322067 Mon Sep 17 00:00:00 2001 From: enigmurl <43832426+enigmurl@users.noreply.github.com> Date: Tue, 7 Jan 2025 19:41:24 -0800 Subject: [PATCH] Finish environment --- src/cheat_sheet.md | 129 +++++++++++++++++++++++++---- src/conditional.md | 65 ++++++++++++++- src/layouts.md | 12 +++ src/modifiers.md | 188 ++++++++++++++++++++++++++++++++++++++++-- src/portal.md | 3 - src/setup.md | 14 +++- src/state.md | 20 +++++ src/text.md | 111 +++++++++++++++++++++++++ src/view_providers.md | 32 +++++-- 9 files changed, 540 insertions(+), 34 deletions(-) diff --git a/src/cheat_sheet.md b/src/cheat_sheet.md index 4e4e7cf..02b8efc 100644 --- a/src/cheat_sheet.md +++ b/src/cheat_sheet.md @@ -1,38 +1,137 @@ # Cheat Sheet -Again, views are the heart of Quarve. There is a decent number of provided views, -of which we will cover commonly used ones. -(really, the standard library has a wide collection of ViewProviders and -IntoViewProviders rather than Views, but we'll generally blur the distinction for sake -of notational convenience). - -Here is a guide Some concepts are mentioned in more detail in later lessons. Treat this lesson more as a -'cheat sheet' of good to know views rather than full explanation. +'cheat sheet' of good to know methods rather than a full explanation. -## General +## Views Construct basic text with the `text` function located in the prelude. ```rust text("rabbit") ``` -All colors are IVPs so -(usually you will also want to set a size, hence the second line) +Display image +```rust +ImageView::new("path_to_resource_in_res_folder") +``` -## Controls +All colors are IVPs (usually you want to set a size, hence the second line) +```rust +BLACK + .intrinsic(100, 100) +``` Dropdown Menu +```rust +let selection = Store::new(None); + +Dropdown::new(selection.binding()) + .option("Alpha") + .option("Beta"); +``` TextField +```rust +let text = Store::new("initial".into()); +TextField::new(text.binding()); +``` -Button +Text Button +```rust +button("Label", |s| {...}) +``` \*There are many controls that one would expect from a UI library that are yet to be added. I apologize for this and may add them in the future if there is interest. ## Layouts -You can use a `vstack`, `hstack`, and `zstack` to c +You can use a `vstack`, `hstack`, or `zstack` to organize content easily. +There are also flex layouts, but these are needed less. + +You can either layout heterogenous data known at compile time,or dynamically +based off of bindings and signals. +```rust +// hetereogenous vstack() + .push(ivp1) + .push(ivp2) + +// binding +binding.binding_vmap(|content, s| text(content.to_string()); + +// signal (slow) +signal.sig_vmap(|content, s| text(content.to_string()); +``` + +## Modifiers + +Apply padding to an ivp +```rust +ivp + .padding(amount) +``` + +Offset the ivp by some amount +```rust +ivp + .offset(dx, dy) +``` -These and other layout views are covered in more detail [here](./layouts.md). +Set the intrinsic size, essentially allocating a rectangle of space. +```rust +ivp + .intrinsic(width, height) +// shorthand for .frame(F.intrinsic(width, height)) +``` + +Setting text attributes +```rust +ivp + .text_font("font_file_in_res/font") + .text_size(size) + .text_color(color) +``` + +Set the background color +```rust +ivp + .bg_color(COLOR) +``` + +Add a foreground view +```rust +ivp + .foreground(attraction) +``` + +Add a background view +```rust +ivp + .background(attraction) +``` + +Lifecycle methods +```rust +ivp + .pre_show(|s| { ... }) // called before children and before being shown + .post_show(|s| { ... }) // called after children and after being shown + .pre_hide(|s| { ... }) // called before children and before being hidden + .post_hide(|s| { ... }) // called after children and after being hidden +``` + +## Conditional + +if else +```rust +view_if(condition_signal, IVP1) + .view_else(IVP2) +``` + +view match +```rust +view_match!(signal; + 0 => arm1ivp, + 1 => arm2ivp, + _ => default_arm_ivp +); +``` diff --git a/src/conditional.md b/src/conditional.md index 6b69d1d..3317c64 100644 --- a/src/conditional.md +++ b/src/conditional.md @@ -3,9 +3,72 @@ Here we show how to show or hide views conditionally. ## If Else -The most simplest conditional is +The simplest conditional is the if (and else) block. These statements +conditionally hide or show views based on the given boolean signal. +Here's the syntax: +```rust +let shown = Store::new(false); +let shown_binding = shown.binding(); + +hstack() + .push(button("toggle color", move |s| { + let curr = *shown_binding.borrow(s); + shown_binding.apply(SetAction::Set(!curr), s); + })) + .push( + // dont like this syntax that much + // but i also think a macro would be overkill + view_if(shown.signal(), BLUE.intrinsic(50, 50)) + // else if would go here + .view_else(RED.intrinsic(50, 50)) + ) +``` ## View Match +A common paradigm is to only show one view out of a set of views, depending on some +state (think about a router). This can be accomplished by the `view_match!` macro. +It's conceptually similar to a match statement based upon the value of some signal. +However, a key distinction is that the different arms are allowed to take different +types. + +The general syntax is as follows +```rust +view_match!(signal; + pattern_1 => IVP1, + pattern_2 => IVP2, + _ => DEFAULT_IVP +) +// sometimes you don't want to match exactly on the content of a signal +// but rather match on the content of a signal after some initial +// operation +view_match!(signal, |content| mapped_expr; + pattern_1 => IVP1, + pattern_2 => IVP2, + _ => DEFAULT_IVP +) +``` + +Here's an example: +```rust +fn mux_demo() -> impl IVP { + let selection = Store::new(None); + let selection_sig = selection.signal(); + + let mux = view_match!(selection_sig, |val: &Option| val.as_ref().map(move |q| q.as_str()); + Some("Alpha") => text("alpha text").bold(), + Some("Beta") => button("beta button", |_s| println!("clicked!")).italic(), + _ => text("Please select an option") + ); + + // macro syntax of vstack + vstack! { + mux; + Dropdown::new(selection.binding()) + .option("Alpha") + .option("Beta"); + } +} +``` The included examples are available in [this project](https://github.com/monocurl/quarve/tree/main/examples/conditional). diff --git a/src/layouts.md b/src/layouts.md index 82e5fa8..76a408e 100644 --- a/src/layouts.md +++ b/src/layouts.md @@ -1 +1,13 @@ # Layouts + +## Types of Layouts + +## Heterogenous layouts + +## Binding + +## Signal + +## LayoutProvider + +## VecLayoutProvider (Advanced) diff --git a/src/modifiers.md b/src/modifiers.md index e74af10..aa573c5 100644 --- a/src/modifiers.md +++ b/src/modifiers.md @@ -1,13 +1,191 @@ # Modifiers Modifiers are methods that transform one IVP into another IVP by adding certain -functionalities. -They are pretty intuitive and allow. +functionalities. They are pretty intuitive and allow for complex user interfaces. We emphasize that the order of modifiers is important. In some cases, code won't even compile if you switch the order. -## Important Modifiers +## Positional Modifiers -This is not an exhaustive list, but we include some commonly used modifiers here. -We hope to add more modifiers in the future to allow for futher customization of views. +Apply padding to an ivp +```rust +ivp + .padding(amount) + +// dynamically based on a signal +ivp + .padding_signal(signal) + +// just a single edge +ivp + .padding_edge(amount, quarve::geo::edge::LEFT | quarve::geo::edge::RIGHT /* or likewise*/) +``` + +Offset the ivp by some amount +```rust +ivp + .offset(dx, dy) +// based on a signal +ivp + .offset_signal(dx_signal, dy_signal) +``` + +In quarve's positioning model, a view has 5 associated sizes: +intrinsic, xsquished, xstretched, ysquished, ystretched. Layouts such as +`vstack` use this information to appropriately position their subviews. + +Sometimes, you may want to manually specify some of these sizing hints, which is +where the `frame` modifier comes in. The formal behavior is a bit hard to describe +, but the essense is that you are "placing" the target IVP into the +given frame. If it doesn't fill up the entir content, +you can also specify which alignment to use. (this is inspired by SwiftUI frame). + +```rust +ivp + .frame(F + .align(Alignment::Leading) + // set both x squished and y squished + // note that by default, the squished and stretched size is the same + // as the intrinsic size + .squished(100, 100) + .intrinsic(200, 200) + .stretched(300, 300) + ) + +ivp + .intrinsic(width, height) +// shorthand for .frame(F.intrinsic(width, height)) +``` + +## Visual Modifiers +You can alter many visual properties about a view using the `layer` modifier + +```rust +ivp + .layer(L + .bg_color(BLUE) + .border(RED, 1) + // corner radius + .radius(4) + // for all of these, you can also use signals + .radius_signal(sig) + ) +// shorthand for border +ivp + .border(RED, 1) +// shorthand for bg_color +ivp + .bg_color(RED) +``` + +Set a cursor for a view +```rust +ivp + .cursor(Cursor::Pointer) +``` + +## Conditional Modifiers +Sometimes, you may only want to apply modifiers conditionally. +For many modifiers of the standard library (but not all), this can be done. + +In particular, we can use the `when` (meta-) modifier that only +applies a set of modifiers conditionally. + +```rust +IVP + .when(cond, |ivp | { ivp + // positional modifiers + .offset(100, 100) + .padding(100) + .frame(F.intrinsic(100, 100)) + // env modifiers + .text_color(RED) + .bold() + // layer + .layer(L.bg_color(RED)) + // foreground or background + .background(RED.layer(L.radius(4))) + // portals + .portal_send(&p, BLUE) + + // however, life cycle methods + // such as .pre_show are not supported + }) +``` + +## Lifecycle Modifiers + +You can get notifications for when a view is mounted or unmounted onto the Window. +Do note that the same view can be mounted and unmounted multiple times. + +```rust +ivp + .pre_show(|s| { ... }) // called before children and before being shown + .post_show(|s| { ... }) // called after children and after being shown + .pre_hide(|s| { ... }) // called before children and before being hidden + .post_hide(|s| { ... }) // called after children and after being hidden +``` + +## Environment Modifiers +Environment modifiers change properties for the entire subtree +of an ivp. The most common ones are related to text. +```rust +vstack() + .push(text("rabbit")) + .push( + text("bunny") + .bold() + ) + .push( + text("strawberry") + // explicitly override the parent environment modifier + .text_size(20) + ) + // set the text size for this entire subtree + // note that vstack is not even text related, + // but we can still apply the modifier here! + .text_size(14) + .italic() +``` + +See [Environment](./environment.md) and [Text](./text.md) lessons for more. + +## Key Listener + +You can see whenever keys are pressed via the key listener method, +which also tells you the currently active modifiers. + +```rust +ivp + .key_listener(|keys, modifiers, s| { + println!("Pressed {:?}", keys) + }) +``` + +## Creating your own Modifiers (Advanced) +Rather than fully implementing your own view provider from scratch, +you can create a view provider that "wraps" another view provider and delegate +most of the view provider methods to the source view provider (composition). +Then, for whatever functionality you want to add, you should properly modify +the necessary methods. +This is one of the advantage of the view provider model: +we can append functionality to a view without allocating more native backings +(which are really expensive). + +Be careful, as if you don't properly call the source view provider method properly, +the observed behavior may be unexpected. Also, in addition to creating a wrapping view provider, you typically create an +associated wrapping IVP as well. + +Example: [ShowHide](https://github.com/monocurl/quarve/blob/main/quarve/src/view/modifers.rs) +modifier (you may have to scroll to `ShowHide`) + +### Provider Modifier +While this approach works, it to instead recommended to implement +the `ProviderModifier`. This is less versatile than the +composite approach, but it allows the modifier to be used in `when` blocks +freely. + +To see a full example of this, take a look at the implementation of the +[Offset](https://github.com/monocurl/quarve/blob/main/quarve/src/view/modifers.rs) modifier +(you may have to scroll down to find `OffsetModifiable` and related structs). diff --git a/src/portal.md b/src/portal.md index b4e2d95..78c4110 100644 --- a/src/portal.md +++ b/src/portal.md @@ -1,8 +1,5 @@ # Portals -**Warning: Portals are experimental and currently quite buggy** - - Portals are (to the best of the knowledge of the authors, which is not that much) a unique contribution of Quarve. diff --git a/src/setup.md b/src/setup.md index 29a6b83..5e641c6 100644 --- a/src/setup.md +++ b/src/setup.md @@ -24,6 +24,12 @@ worse performance compared to Cocoa. E.g. `C:\Qt\6.8.1\msvc2022_64`. 2. Append the binaries folder to your `PATH`. E.g. `C:\Qt\6.8.1\msvc2022_64\bin`. +*Linux:* +[Install Qt](https://www.qt.io/download-dev) +1. Set the environment variable `QUARVE_BACKEND_PATH` to the root of Qt installation. +E.g. `~/Qt/6.8.1/gcc_64`. +2. Append the library folder to to your e`PATH`. E.g. `~/Qt/6.8.1/gcc_64/bin/` + *macOS:* [Install Qt](https://www.qt.io/download-dev) 1. SET the environment variable `QUARVE_BACKEND_PATH` to the root of Qt installation. @@ -67,6 +73,12 @@ quarve deploy -n ``` After deploying, a target application will be created in the `quarve_target` directory. As of now, we do not know do executable packing so for Windows and Linux you will -have to do to that yourself. For macOS, you may have to update the `Info.plist` file to +have to do to that yourself (and may want to change directory structure as well). +For macOS, you may have to update the `Info.plist` file to e.g. customize the application icon. Depending on your intended distribution style, you may have to codesign the application yourself too. + +## Debugging + +On Linux, if you get the error that `` is not found (or something similar), +it means that you must install the OpenGL dev kit. diff --git a/src/state.md b/src/state.md index e69de29..a4701f2 100644 --- a/src/state.md +++ b/src/state.md @@ -0,0 +1,20 @@ +# State +The way state is done is extremely important to the (theoretical at least) +efficiency of Quarve. It is partially inspired by Group Theory. + +## Signals + +### Signal Listeners + +## Store +The heart of state is a Store. + +## Binding + +### Action Listen + +## Undo + +## Derived Store + +## Stateful diff --git a/src/text.md b/src/text.md index e69de29..033ecea 100644 --- a/src/text.md +++ b/src/text.md @@ -0,0 +1,111 @@ +# Text + +The + +## Labels + +The easiest way to create text is through the `text` function in the prelude +```rust +text("Rabbit") +``` + +You can also have a label that dynamically updates based on a signal +```rust +Text::from_signal(signal) +``` + +## TextField + +A TextField can be styled similarly to Text. TextField connect their content changes +to a binding. +```rust +let text = Store::new("initial".into()); +TextField::new(text.binding()); +``` + +I don't like the focus system, but you can control when a TextField is focused +using a `TokenStore` (this is definitely subject to be changed in the future). + +```rust +let focused = TokenStore::new(None); +let text1 = Store::new("alpha".into()); +let text2 = Store::new("beta".into()); + +vstack() + .push( + TextField::new(text1.binding()) + .focused_if_eq(focused.binding(), 1) + ) + .push( + TextField::new(text2.binding()) + .focused_if_eq(focused.binding(), 2) + ); +``` + +\* Right now there is not that much control over the textfield in terms of its +appearance. We hope to possibly improve this in the future. + +## Environment + +You can specify different text settings using environment modifiers, such as below. +These affect `Text` and `TextField` views. +```rust +IVP + .bold() + .italic() + .text_font("font_file_in_res/font") + .text_size(size) + .text_color(color) +``` + +Note that environment modifiers are somewhat different than regular modifiers +in that they apply to all subviews rather than just the modifed IVP. +See the [environment lesson](./environment.md) to learn more. + +## TextView (Advanced) +**TODO this section is currently poorly written and lacking much-needed visuals** + +TextView is a heavy-duty text editor. It works by following an attribute model. + +The state of a TextView is comprised of a series of pages. Each page has a series of runs +(also called blocks/paragraphs in other frameworks). Each run has a single string +of content, with no new lines. In addition, you can store attributes for each page, +run, or character. This is useful since, for example, the attributes can be used +to style the appeareance and layout of the characters. Moreover, you may simply +want to store custom attributes for your own application, irrespective of causing +a visual change. + +There are two types of attributes: derived and intrinsic. +This is analogous to derived and normal stores, namely that the derived attributes +can be thought of solely as a function of the text content, but the intrinsic attributes +may vary even for the same text content. As an example, an auto markdown formatter +would only have derived attributes since the visual display is solely a function of the text. +An example of an intrinsic attribute would be something like a rich text editor +where we can artificially bold certain content without changing the text. +(this description is simplified). + +Note that all TextViews layout their content vertically from the first page +to the last. Moreover, each page has its own separate native editor +(if you don't want this, simply use a single page). + +**TODO talk about cursor and cursor state** + +To actually create a TextView, you implement the `TextViewProvider` trait. Here, +you specify the intrinsic and derived attributes. You also specify +properties about how the TextView is displayed. For instance, you can add a background +to each page. You can also specify run decorations (most notably, attaching a line number +in the gutter of each line) quite easily. Finally, you can attach a decoration +to the cursor, which is useful for displaying an autocomplete window. + +As there is a lot of machinery for text views, we omit an inline example +and instead refer you to this +[full example](https://github.com/monocurl/quarve/blob/main/examples/textview/src/main.rs). + +### Limitations +Currently TextView only supports the same font and font size for the entirety +of the text. We only support content modification to be done on the main thread, +(attributes can be changed in any thread). + +While in theory TextView should be extremely efficient, +I need to still do some optimizations to improve performance for certain scenarios. +Files <= 1000 lines long should be fine, though. diff --git a/src/view_providers.md b/src/view_providers.md index 34524ff..07507ab 100644 --- a/src/view_providers.md +++ b/src/view_providers.md @@ -12,16 +12,14 @@ backing and appropriately filling it with content Most of the time, you don't actually have to code your own view provider, but instead reuse existing ones. Consequently, rather than implementing the ViewProvider trait, -you will more likely implement the **IntoViewProvider** trait. +you will more likely implement the **IntoViewProvider** trait (abbreviated IVP). Let's take a concrete example to highlight the distinction. Here, we have a -As another example, while a color (such as BLACK) does not know how to control - -## Beginner - -ViewProviders. +As another example, while a color (such as `BLACK`) does not know how to control a view, +it knows how to convert itself into a ViewProvider that appropriately displays +a black view. Hence, all colors are IVPs. ### IntoViewProvider Rather than rewriting every time, we would instead like to instead use. @@ -33,8 +31,24 @@ Functional Struct UpContext and DownContext are related to ways of passing information. -Don't worry about them too much, they are explained more in the advanced section. +Don't worry about them too much, they are explained more in the next section. + +## ViewProvider +Most of the time, you never have to actually implement a full view provider yourself, +but sometimes you may have to implement parts of it (i.e. ). It's also good to know +the general layout model. + +The ViewProvider has a few main roles (there are other minor things it does, +such as modifying environment). +1. To create and properly manage the native backing +2. To add subviews and properly lay them out +3. To respond to system and lifecycle events +4. To give proper sizing hints to the parent view so that it can layout this view + +### Init Backing + +### Layout Model -## Advanced +// also sizes -We recommend you come back to this section once you have explored more of Quarve. +### Event Handling