From dfa95a1ececdee7430d465efeebdae8b0aea3634 Mon Sep 17 00:00:00 2001 From: outfoxxed Date: Tue, 12 Mar 2024 05:29:39 -0700 Subject: [PATCH] guide: add intro --- content/docs/configuration/intro.md | 516 +++++++++++++++++++++ content/docs/configuration/qml-overview.md | 56 ++- 2 files changed, 568 insertions(+), 4 deletions(-) create mode 100644 content/docs/configuration/intro.md diff --git a/content/docs/configuration/intro.md b/content/docs/configuration/intro.md new file mode 100644 index 0000000..f481d06 --- /dev/null +++ b/content/docs/configuration/intro.md @@ -0,0 +1,516 @@ ++++ +title = "Introduction" ++++ + +This page will walk you through the process of creating a simple bar/panel, and +introduce you to all the basic concepts involved. + +There are many links to the [QML Overview](../qml-overview) +and [Type Reference](/docs/types) which you should follow if you don't +fully understand the concepts involved. + +## Shell Files + +Every quickshell instance starts from a shell root file, conventionally named `shell.qml`. +The default path is `~/.config/quickshell/shell.qml`. +(where `~/.config` can be substituted with `$XDG_CONFIG_HOME` if present.) + +Each shell file starts with the shell root object. Only one may exist per configuration. + +```qml {filename="~/.config/quickshell/shell.qml"} +import Quickshell + +ShellRoot { + // ... +} +``` + +The shell root is not a visual element but instead contains all of the visual +and non visual objects in your shell. You can have multiple different shells +with shared components and different shell roots. + +{{% details title="Shell search paths and manifests" closed="true" %}} + +Quickshell can be launched with configurations in locations other than the default one. + +The `-p` or `--path` option will launch the shell root at the given path. +It will also accept folders with a `shell.qml` file in them. +It can also be specified via the `QS_CONFIG_PATH` environment variable. + +The `-c` or `--config` option will launch a configuration from the current manifest, +or if no manifest is specified, a subfolder of quickshell's base path. +It can also be specified via the `QS_CONFIG_NAME` environment variable. + +The base path defaults to `~/.config/quickshell`, but can be changed using +the `QS_BASE_PATH` environment variable. + +The `-m` or `--manifest` option specifies the quickshell manifest to read configs +from. When used with `-c`, the config will be chosen by name from the manifest. +It can also be specified via the `QS_MANIFEST` environment variable. + +The manifest path defaults to `~/.config/quickshell/manifest.conf` and is a list +of `name = path` pairs where path can be relative or absolute. +Lines starting with `#` are comments. + +```properties +# ~/.config/quickshell/manifest.conf +myconf1 = myconf +myconf2 = ./myconf +myconf3 = myconf/shell.nix +myconf4 = ~/.config/quickshell/myconf +``` + +You can use `quickshell --current` to print the current values of any of these +options and what set them. + +{{% /details %}} + +## Creating Windows + +Quickshell has two main window types available, +[PanelWindow](/docs/types/quickshell/panelwindow) for bars and widgets, and +[FloatingWindow](/docs/types/quickshell/floatingwindow) for standard desktop windows. + +We'll start with an example: +```qml +import Quickshell // for ShellRoot and PanelWindow +import QtQuick // for Text + +ShellRoot { + PanelWindow { + anchors { + top: true + left: true + right: true + } + + height: 30 + + Text { + // center the bar in its parent component (the window) + anchors.centerIn: parent + + text: "hello world" + } + } +} +``` + +The above example creates a bar/panel on your currently focused monitor with +a centered piece of [text](https://doc.qt.io/qt-6/qml-qtquick-text.html). It will also reserve space for itself on your monitor. + +More information about available properties is available in the [type reference](/docs/types/quickshell/panelwindow). + +## Running a process + +Now that we have a piece of text, what if it did something useful? +To start with lets make a clock. To get the time we'll use the `date` command. + +We can use a [Process](/docs/types/quickshell.io/process) object to run commands +and return their results. + +We'll listen to the [DataStreamParser.read](/docs/types/quickshell.io/datastreamparser/#signal.read) +[signal](/docs/configuration/qml-overview/#signals) emitted by +[SplitParser](/docs/types/quickshell.io/splitparser/) using a +[signal handler](/docs/configuration/qml-overview/#signal-handlers) +to update the text on the clock. + +{{< callout type="info" >}} +Note: Quickshell live-reloads your code. You can leave it open and edit the +original file. The panel will reload when you save it. +{{< /callout >}} + +```qml +import Quickshell +import Quickshell.Io // for Process +import QtQuick + +ShellRoot { + PanelWindow { + anchors { + top: true + left: true + right: true + } + + height: 30 + + Text { + // give the text an ID we can refer to elsewhere in the file + id: clock + + anchors.centerIn: parent + + // create a process management object + Process { + // the command it will run, every argument is its own string + command: ["date"] + + // run the command immediately + running: true + + // process the stdout stream using a SplitParser + // which returns chunks of output after a delimiter + stdout: SplitParser { + // listen for the read signal, which returns the data that was read + // from stdout, then write that data to the clock's text property + onRead: data => clock.text = data + } + } + } + } +} +``` + +## Running code at an interval +With the above example, your bar should now display the time, but it isn't updating! +Let's use a [Timer](https://doc.qt.io/qt-6/qml-qtqml-timer.html) fix that. + +```qml +import Quickshell +import Quickshell.Io +import QtQuick + +ShellRoot { + PanelWindow { + anchors { + top: true + left: true + right: true + } + + height: 30 + + Text { + id: clock + anchors.centerIn: parent + + Process { + // give the process object an id so we can talk + // about it from the timer + id: dateProc + + command: ["date"] + running: true + + stdout: SplitParser { + onRead: data => clock.text = data + } + } + + // use a timer to rerun the process at an interval + Timer { + // 1000 milliseconds is 1 second + interval: 1000 + + // start the timer immediately + running: true + + // run the timer again when it ends + repeat: true + + // when the timer is triggered, set the running property of the + // process to true, which reruns it if stopped. + onTriggered: dateProc.running = true + } + } + } +} +``` + +## Reuseable components + +If you have multiple monitors you might have noticed that your bar +is only on one of them. If not, you'll still want to **follow this section +to make sure your bar dosen't disappear if your monitor disconnects**. + +We can use a [Variants](http://localhost:1313/docs/types/quickshell/variants/) +object to create instances of *non widget items*. +(See [Repeater](https://doc.qt.io/qt-6/qml-qtquick-repeater.html) for doing +something similar with visual items.) + +The `Variants` type creates instances of a +[Component](https://doc.qt.io/qt-6/qml-qtqml-component.html) based on a data model +you supply. (A component is a re-usable tree of objects.) + +The most common use of `Variants` in a shell is to create instances of +a window (your bar) based on your monitor list (the data model). + +Variants will inject the properties in the data model directly into the component, +meaning we can easily set the screen property of our bar +(See [Window.screen](/docs/types/quickshell/qswindow/#prop.screen).) + +```qml +import Quickshell +import Quickshell.Io +import QtQuick + +ShellRoot { + Variants { + variants: { + // get the list of screens from the Quickshell singleton + const screens = Quickshell.screens; + + // transform the screen list into a list of objects with + // screen variables, which will be set for each created object + const variants = screens.map(screen => { + return { screen: screen }; + }); + + return variants; + } + + component: Component { + PanelWindow { + // the screen property will be injected into the window, + // so each bar displays on the right monitor + + anchors { + top: true + left: true + right: true + } + + height: 30 + + Text { + id: clock + anchors.centerIn: parent + + Process { + id: dateProc + command: ["date"] + running: true + + stdout: SplitParser { + onRead: data => clock.text = data + } + } + + Timer { + interval: 1000 + running: true + repeat: true + onTriggered: dateProc.running = true + } + } + } + } + } +} +``` + +See also: +[Property Bindings](/docs/configuration/qml-overview/#property-bindings), +[Variants.component](/docs/types/quickshell/variants/#prop.component), +[Quickshell.screens](/docs/types/quickshell/quickshell/#prop.screens), +[Array.map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map) + + +With this example, bars will be created and destroyed as you plug and unplug them, +due to the reactive nature of the +[Quickshell.screens](/docs/types/quickshell/quickshell/#prop.screens) property. +(See: [Reactive Bindings](/docs/configuration/qml-overview/#reactive-bindings).) + +Now there's an important problem you might have noticed: when the window +is created multiple times we also make a new Process and Timer. We can fix +this by moving the Process and Timer outside of the window. + +{{< callout type="error" >}} +This code will not work correctly. +{{< /callout >}} + +```qml +import Quickshell +import Quickshell.Io +import QtQuick + +ShellRoot { + Variants { + variants: Quickshell.screens.map(screen => ({ screen })) + + component: Component { + PanelWindow { + anchors { + top: true + left: true + right: true + } + + height: 30 + + Text { + id: clock + anchors.centerIn: parent + } + } + } + } + + Process { + id: dateProc + command: ["date"] + running: true + + stdout: SplitParser { + onRead: data => clock.text = data + } + } + + Timer { + interval: 1000 + running: true + repeat: true + onTriggered: dateProc.running = true + } +} +``` + +However there is a problem with naively moving the Process and Timer +out of the component. +*What about the `clock` that the process references?* + +If you run the above example you'll see something like this in the console every second: + +``` +file:///home/name/.config/quickshell/shell.qml:33: ReferenceError: clock is not defined +file:///home/name/.config/quickshell/shell.qml:33: ReferenceError: clock is not defined +file:///home/name/.config/quickshell/shell.qml:33: ReferenceError: clock is not defined +file:///home/name/.config/quickshell/shell.qml:33: ReferenceError: clock is not defined +file:///home/name/.config/quickshell/shell.qml:33: ReferenceError: clock is not defined +``` + +This is because the `clock` object, even though it has an ID, cannot be referenced +outside of its component. Remember, components can be created *any number of times*, +including zero, so `clock` may not exist or there may be more than one, meaning +there isn't an object to refer to from here. + +We can fix it with a [Property Definition](/docs/configuration/qml-overview/#property-definitions). + +We can define a property inside of the ShellRoot and reference it from the clock +text instead. Due to QML's [Reactive Bindings](/docs/configuration/qml-overview/#reactive-bindings), +the clock text will be updated when we update the property for every clock that +currently exists. + +```qml +import Quickshell +import Quickshell.Io +import QtQuick + +ShellRoot { + id: root + + // add a property in the root + property string time; + + Variants { + variants: Quickshell.screens.map(screen => ({ screen })) + + component: Component { + PanelWindow { + anchors { + top: true + left: true + right: true + } + + height: 30 + + Text { + // remove the id as we don't need it anymore + + anchors.centerIn: parent + + // bind the text to the root's time property + text: root.time + } + } + } + } + + Process { + id: dateProc + command: ["date"] + running: true + + stdout: SplitParser { + // update the property instead of the clock directly + onRead: data => root.time = data + } + } + + Timer { + interval: 1000 + running: true + repeat: true + onTriggered: dateProc.running = true + } +} +``` + +Now we've fixed the problem so there's nothing actually wrong with the +above code, but we can make it more concise: + +1. `Component`s can be defined implicitly, meaning we can remove the +component wrapping the window and place the window directly into the +`component` property. +2. The [Variants.component](/docs/types/quickshell/variants/#prop.component) +property is a [Default Property](/docs/configuration/qml-overview/#the-default-property), +which means we can skip the `component: ` part of the assignment. +We're already using [ShellRoot](/docs/types/quickshell/shellroot/)'s +default property to store our Variants, Process, and Timer components +among other things. +3. The ShellRoot dosen't actually need an `id` property to talk about +the time property, as it is the outermost object in the file which +has [special scoping rules](/docs/configuration/qml-overview/#property-access-scopes). + +This is what our shell looks like with the above (optional) cleanup: + +```qml +import Quickshell +import Quickshell.Io +import QtQuick + +ShellRoot { + property string time; + + Variants { + variants: Quickshell.screens.map(screen => ({ screen })) + + PanelWindow { + anchors { + top: true + left: true + right: true + } + + height: 30 + + Text { + anchors.centerIn: parent + + // now just time instead of root.time + text: time + } + } + } + + Process { + id: dateProc + command: ["date"] + running: true + + stdout: SplitParser { + // now just time instead of root.time + onRead: data => time = data + } + } + + Timer { + interval: 1000 + running: true + repeat: true + onTriggered: dateProc.running = true + } +} +``` diff --git a/content/docs/configuration/qml-overview.md b/content/docs/configuration/qml-overview.md index 761df7b..f0981f4 100644 --- a/content/docs/configuration/qml-overview.md +++ b/content/docs/configuration/qml-overview.md @@ -141,7 +141,7 @@ specifying a version at least when importing quickshell modules. #### Implicit imports -The QML engine will automatically import any [types](#types) in neighboring files +The QML engine will automatically import any [types](#creating-types) in neighboring files with names that start with an uppercase letter. ``` @@ -222,10 +222,9 @@ Properties can be defined inside of objects with the following syntax: [required] [readonly] [default] property [: binding] ``` -- `required` forces users of this type to assign this property. See [Types](#types) for details. +- `required` forces users of this type to assign this property. See [Creating Types](#creating-types) for details. - `readonly` makes the property not assignable. Its binding will still be [reactive](#reactive-bindings). -- `default` makes the property the default property of this type. See [Types](#types) -for details. +- `default` makes the property the [default property](#the-default-property) of this type. - `type` is the type of the property. You can use `var` if you don't know or don't care but be aware that `var` will allow any value type. - `name` is the name that the property is known as. It cannot start with an uppercase letter. @@ -247,6 +246,39 @@ Item { Defining a property with the same name as one provided by the current object will override the property of the type it is derived from in the current context. +##### The default property + +Types can have a *default property* which must accept either an object or a list of objects. + +The default property will allow you to assign a value to it without using the name of the property: +```qml +Item { + // normal property + foo: 3 + + // this item is assigned to the outer object's default property + Item { + } +} +``` + +If the default property is a list, you can put multiple objects into it the same way as you +would put a single object in: +```qml +Item { + // normal property + foo: 3 + + // this item is assigned to the outer object's default property + Item { + } + + // this one is too + Item { + } +} +``` + ##### The `id` property Every object has a special property called `id` that can be assigned to give @@ -642,6 +674,22 @@ Item { } ``` +##### Singletons +QML Types can be easily made into a singleton, meaning there is only one instance +of the type. + +To make a type a singleton, put `pragma Singleton` at the top of the file. + +```qml +pragma Singleton +import ... + +Item { ... } +``` + +once a type is a singleton, its members can be accessed by name from neighboring +files. + ## Concepts ### Reactive bindings