mirror of
https://gitlab.gnome.org/jwestman/blueprint-compiler.git
synced 2025-06-22 23:19:25 -04:00
Compare commits
17 commits
080607cc30
...
33c8ad03db
Author | SHA1 | Date | |
---|---|---|---|
|
33c8ad03db | ||
|
5c7fb03da7 | ||
|
2e42dc6848 | ||
|
a12d3f5c81 | ||
|
a83c7e936d | ||
|
3816f4fe8d | ||
|
e9d61cb6f9 | ||
|
e1f972ef16 | ||
|
13e477aa25 | ||
|
ba8ec80456 | ||
|
0fe58ffc37 | ||
|
14d1892254 | ||
|
c74c5ac232 | ||
|
9e293a31e6 | ||
|
cce1af5f09 | ||
|
4dd55ab2aa | ||
|
52e651a168 |
22 changed files with 623 additions and 30 deletions
|
@ -3,7 +3,7 @@ stages:
|
||||||
- pages
|
- pages
|
||||||
|
|
||||||
build:
|
build:
|
||||||
image: registry.gitlab.gnome.org/jwestman/blueprint-compiler
|
image: registry.gitlab.gnome.org/gnome/blueprint-compiler
|
||||||
stage: build
|
stage: build
|
||||||
script:
|
script:
|
||||||
- black --check --diff ./ tests
|
- black --check --diff ./ tests
|
||||||
|
@ -33,7 +33,7 @@ build:
|
||||||
path: coverage.xml
|
path: coverage.xml
|
||||||
|
|
||||||
fuzz:
|
fuzz:
|
||||||
image: registry.gitlab.gnome.org/jwestman/blueprint-compiler
|
image: registry.gitlab.gnome.org/gnome/blueprint-compiler
|
||||||
stage: build
|
stage: build
|
||||||
script:
|
script:
|
||||||
- meson _build
|
- meson _build
|
||||||
|
|
27
blueprint-compiler.doap
Normal file
27
blueprint-compiler.doap
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
<Project xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#"
|
||||||
|
xmlns:foaf="http://xmlns.com/foaf/0.1/"
|
||||||
|
xmlns:gnome="http://api.gnome.org/doap-extensions#"
|
||||||
|
xmlns="http://usefulinc.com/ns/doap#">
|
||||||
|
|
||||||
|
<name xml:lang="en">Blueprint</name>
|
||||||
|
<shortdesc xml:lang="en">A modern language for creating GTK interfaces</shortdesc>
|
||||||
|
<description xml:lang="en">Blueprint is a language and associated tooling for building user interfaces for GTK.</description>
|
||||||
|
<category rdf:resource="http://api.gnome.org/doap-extensions#apps" />
|
||||||
|
<programming-language>Python</programming-language>
|
||||||
|
|
||||||
|
<homepage
|
||||||
|
rdf:resource="https://gnome.gitlab.gnome.org/blueprint-compiler/" />
|
||||||
|
<download-page
|
||||||
|
rdf:resource="https://gitlab.gnome.org/GNOME/blueprint-compiler/-/releases" />
|
||||||
|
<bug-database
|
||||||
|
rdf:resource="https://gitlab.gnome.org/GNOME/blueprint-compiler/issues" />
|
||||||
|
|
||||||
|
<maintainer>
|
||||||
|
<foaf:Person>
|
||||||
|
<foaf:name>James Westman</foaf:name>
|
||||||
|
<foaf:mbox rdf:resource="mailto:james@jwestman.net" />
|
||||||
|
<gnome:userid>jwestman</gnome:userid>
|
||||||
|
</foaf:Person>
|
||||||
|
</maintainer>
|
||||||
|
</Project>
|
|
@ -255,7 +255,11 @@ def decompile_element(
|
||||||
|
|
||||||
ctx._node_stack.append(xml)
|
ctx._node_stack.append(xml)
|
||||||
ctx.start_block()
|
ctx.start_block()
|
||||||
gir = decompiler(*args, **kwargs)
|
|
||||||
|
try:
|
||||||
|
gir = decompiler(*args, **kwargs)
|
||||||
|
except TypeError as e:
|
||||||
|
raise UnsupportedError(tag=xml.tag)
|
||||||
|
|
||||||
if not decompiler._skip_children:
|
if not decompiler._skip_children:
|
||||||
for child in xml.children:
|
for child in xml.children:
|
||||||
|
@ -266,8 +270,6 @@ def decompile_element(
|
||||||
|
|
||||||
except UnsupportedError as e:
|
except UnsupportedError as e:
|
||||||
raise e
|
raise e
|
||||||
except TypeError as e:
|
|
||||||
raise UnsupportedError(tag=xml.tag)
|
|
||||||
|
|
||||||
|
|
||||||
def decompile(data: str) -> str:
|
def decompile(data: str) -> str:
|
||||||
|
|
|
@ -219,7 +219,7 @@ def report_bug(): # pragma: no cover
|
||||||
f"""{Colors.BOLD}{Colors.RED}***** COMPILER BUG *****
|
f"""{Colors.BOLD}{Colors.RED}***** COMPILER BUG *****
|
||||||
The blueprint-compiler program has crashed. Please report the above stacktrace,
|
The blueprint-compiler program has crashed. Please report the above stacktrace,
|
||||||
along with the input file(s) if possible, on GitLab:
|
along with the input file(s) if possible, on GitLab:
|
||||||
{Colors.BOLD}{Colors.BLUE}{Colors.UNDERLINE}https://gitlab.gnome.org/jwestman/blueprint-compiler/-/issues/new?issue
|
{Colors.BOLD}{Colors.BLUE}{Colors.UNDERLINE}https://gitlab.gnome.org/GNOME/blueprint-compiler/-/issues/new?issue
|
||||||
{Colors.CLEAR}"""
|
{Colors.CLEAR}"""
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -71,7 +71,7 @@ def decompile_file(in_file, out_file) -> T.Union[str, CouldNotPort]:
|
||||||
print(
|
print(
|
||||||
f"""{Colors.FAINT}Either the original XML file had an error, or there is a bug in the
|
f"""{Colors.FAINT}Either the original XML file had an error, or there is a bug in the
|
||||||
porting tool. If you think it's a bug (which is likely), please file an issue on GitLab:
|
porting tool. If you think it's a bug (which is likely), please file an issue on GitLab:
|
||||||
{Colors.BLUE}{Colors.UNDERLINE}https://gitlab.gnome.org/jwestman/blueprint-compiler/-/issues/new?issue{Colors.CLEAR}\n"""
|
{Colors.BLUE}{Colors.UNDERLINE}https://gitlab.gnome.org/GNOME/blueprint-compiler/-/issues/new?issue{Colors.CLEAR}\n"""
|
||||||
)
|
)
|
||||||
|
|
||||||
return CouldNotPort("does not compile")
|
return CouldNotPort("does not compile")
|
||||||
|
@ -136,7 +136,7 @@ def step1():
|
||||||
wrap.write(
|
wrap.write(
|
||||||
f"""[wrap-git]
|
f"""[wrap-git]
|
||||||
directory = blueprint-compiler
|
directory = blueprint-compiler
|
||||||
url = https://gitlab.gnome.org/jwestman/blueprint-compiler.git
|
url = https://gitlab.gnome.org/GNOME/blueprint-compiler.git
|
||||||
revision = {VERSION}
|
revision = {VERSION}
|
||||||
depth = 1
|
depth = 1
|
||||||
|
|
||||||
|
|
|
@ -60,19 +60,21 @@ class ScopeCtx:
|
||||||
|
|
||||||
passed = {}
|
passed = {}
|
||||||
for obj in self._iter_recursive(self.node):
|
for obj in self._iter_recursive(self.node):
|
||||||
if obj.tokens["id"] is None:
|
from .gtk_menu import Menu
|
||||||
|
|
||||||
|
if not (isinstance(obj, Object) or isinstance(obj, Menu)) or obj.id is None:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if obj.tokens["id"] in passed:
|
if obj.id in passed:
|
||||||
token = obj.group.tokens["id"]
|
token = obj.group.tokens["id"]
|
||||||
if not isinstance(obj, Template) and not isinstance(
|
if not isinstance(obj, Template) and not isinstance(
|
||||||
obj, ExtListItemFactory
|
obj, ExtListItemFactory
|
||||||
):
|
):
|
||||||
raise CompileError(
|
raise CompileError(
|
||||||
f"Duplicate object ID '{obj.tokens['id']}'",
|
f"Duplicate object ID '{obj.id}'",
|
||||||
token.range,
|
token.range,
|
||||||
)
|
)
|
||||||
passed[obj.tokens["id"]] = obj
|
passed[obj.id] = obj
|
||||||
|
|
||||||
def _iter_recursive(self, node: AstNode):
|
def _iter_recursive(self, node: AstNode):
|
||||||
yield node
|
yield node
|
||||||
|
|
|
@ -302,12 +302,18 @@ expr.children = [
|
||||||
|
|
||||||
@decompiler("lookup", skip_children=True, cdata=True)
|
@decompiler("lookup", skip_children=True, cdata=True)
|
||||||
def decompile_lookup(
|
def decompile_lookup(
|
||||||
ctx: DecompileCtx, gir: gir.GirContext, cdata: str, name: str, type: str
|
ctx: DecompileCtx,
|
||||||
|
gir: gir.GirContext,
|
||||||
|
cdata: str,
|
||||||
|
name: str,
|
||||||
|
type: T.Optional[str] = None,
|
||||||
):
|
):
|
||||||
if ctx.parent_node is not None and ctx.parent_node.tag == "property":
|
if ctx.parent_node is not None and ctx.parent_node.tag == "property":
|
||||||
ctx.print("expr ")
|
ctx.print("expr ")
|
||||||
|
|
||||||
if t := ctx.type_by_cname(type):
|
if type is None:
|
||||||
|
type = ""
|
||||||
|
elif t := ctx.type_by_cname(type):
|
||||||
type = decompile.full_name(t)
|
type = decompile.full_name(t)
|
||||||
else:
|
else:
|
||||||
type = "$" + type
|
type = "$" + type
|
||||||
|
@ -327,7 +333,7 @@ def decompile_lookup(
|
||||||
if constant == ctx.template_class:
|
if constant == ctx.template_class:
|
||||||
ctx.print("template." + name)
|
ctx.print("template." + name)
|
||||||
elif constant == "":
|
elif constant == "":
|
||||||
ctx.print("item as <" + type + ">." + name)
|
ctx.print(f"item as <{type}>.{name}")
|
||||||
else:
|
else:
|
||||||
ctx.print(constant + "." + name)
|
ctx.print(constant + "." + name)
|
||||||
return
|
return
|
||||||
|
|
|
@ -225,8 +225,14 @@ class Signal(AstNode):
|
||||||
|
|
||||||
|
|
||||||
@decompiler("signal")
|
@decompiler("signal")
|
||||||
def decompile_signal(ctx, gir, name, handler, swapped=None, after="false", object=None):
|
def decompile_signal(
|
||||||
|
ctx: DecompileCtx, gir, name, handler, swapped=None, after="false", object=None
|
||||||
|
):
|
||||||
object_name = object or ""
|
object_name = object or ""
|
||||||
|
|
||||||
|
if object_name == ctx.template_class:
|
||||||
|
object_name = "template"
|
||||||
|
|
||||||
name = name.replace("_", "-")
|
name = name.replace("_", "-")
|
||||||
line = f"{name} => ${handler}({object_name})"
|
line = f"{name} => ${handler}({object_name})"
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
#
|
#
|
||||||
# SPDX-License-Identifier: LGPL-3.0-or-later
|
# SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
|
|
||||||
""" Utilities for parsing an AST from a token stream. """
|
"""Utilities for parsing an AST from a token stream."""
|
||||||
|
|
||||||
import typing as T
|
import typing as T
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
|
|
|
@ -9,7 +9,7 @@ from pathlib import Path
|
||||||
|
|
||||||
__all__ = ["get_docs_section"]
|
__all__ = ["get_docs_section"]
|
||||||
|
|
||||||
DOCS_ROOT = "https://jwestman.pages.gitlab.gnome.org/blueprint-compiler"
|
DOCS_ROOT = "https://gnome.pages.gitlab.gnome.org/blueprint-compiler"
|
||||||
|
|
||||||
|
|
||||||
sections: dict[str, "Section"] = {}
|
sections: dict[str, "Section"] = {}
|
||||||
|
|
|
@ -16,7 +16,7 @@ a module in your flatpak manifest:
|
||||||
"sources": [
|
"sources": [
|
||||||
{
|
{
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://gitlab.gnome.org/jwestman/blueprint-compiler",
|
"url": "https://gitlab.gnome.org/GNOME/blueprint-compiler",
|
||||||
"tag": "v0.16.0"
|
"tag": "v0.16.0"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
@ -59,7 +59,7 @@ Features
|
||||||
Links
|
Links
|
||||||
-----
|
-----
|
||||||
|
|
||||||
- `Source code <https://gitlab.gnome.org/jwestman/blueprint-compiler>`_
|
- `Source code <https://gitlab.gnome.org/GNOME/blueprint-compiler>`_
|
||||||
- `Workbench <https://github.com/sonnyp/Workbench>`_ lets you try, preview and export Blueprint
|
- `Workbench <https://github.com/sonnyp/Workbench>`_ lets you try, preview and export Blueprint
|
||||||
- `GNOME Builder <https://developer.gnome.org/documentation/introduction/builder.html>`_ provides builtin support
|
- `GNOME Builder <https://developer.gnome.org/documentation/introduction/builder.html>`_ provides builtin support
|
||||||
- `Vim syntax highlighting plugin by thetek42 <https://github.com/thetek42/vim-blueprint-syntax>`_
|
- `Vim syntax highlighting plugin by thetek42 <https://github.com/thetek42/vim-blueprint-syntax>`_
|
||||||
|
|
|
@ -10,7 +10,7 @@ Properties are the main way to set values on objects, but they are limited by th
|
||||||
|
|
||||||
Extensions are a feature of ``Gtk.Buildable``--see `Gtk.Buildable.custom_tag_start() <https://docs.gtk.org/gtk4/vfunc.Buildable.custom_tag_start.html>`_ for internal details.
|
Extensions are a feature of ``Gtk.Buildable``--see `Gtk.Buildable.custom_tag_start() <https://docs.gtk.org/gtk4/vfunc.Buildable.custom_tag_start.html>`_ for internal details.
|
||||||
|
|
||||||
Because they aren't part of the type system, they aren't present in typelib files like properties and signals are. Therefore, if a library adds a new extension, syntax for it must be added to Blueprint manually. If there's a commonly used extension that isn't supported by Blueprint, please `file an issue <https://gitlab.gnome.org/jwestman/blueprint-compiler/-/issues>`_.
|
Because they aren't part of the type system, they aren't present in typelib files like properties and signals are. Therefore, if a library adds a new extension, syntax for it must be added to Blueprint manually. If there's a commonly used extension that isn't supported by Blueprint, please `file an issue <https://gitlab.gnome.org/GNOME/blueprint-compiler/-/issues>`_.
|
||||||
|
|
||||||
.. rst-class:: grammar-block
|
.. rst-class:: grammar-block
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@ Setting up Blueprint on a new or existing project
|
||||||
Using the porting tool
|
Using the porting tool
|
||||||
~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
Clone `blueprint-compiler <https://gitlab.gnome.org/jwestman/blueprint-compiler>`_
|
Clone `blueprint-compiler <https://gitlab.gnome.org/GNOME/blueprint-compiler>`_
|
||||||
from source. You can install it using ``meson _build`` and ``ninja -C _build install``,
|
from source. You can install it using ``meson _build`` and ``ninja -C _build install``,
|
||||||
or you can leave it uninstalled.
|
or you can leave it uninstalled.
|
||||||
|
|
||||||
|
@ -29,7 +29,7 @@ blueprint-compiler works as a meson subproject.
|
||||||
|
|
||||||
[wrap-git]
|
[wrap-git]
|
||||||
directory = blueprint-compiler
|
directory = blueprint-compiler
|
||||||
url = https://gitlab.gnome.org/jwestman/blueprint-compiler.git
|
url = https://gitlab.gnome.org/GNOME/blueprint-compiler.git
|
||||||
revision = main
|
revision = main
|
||||||
depth = 1
|
depth = 1
|
||||||
|
|
||||||
|
|
481
docs/tutorial.rst
Normal file
481
docs/tutorial.rst
Normal file
|
@ -0,0 +1,481 @@
|
||||||
|
========
|
||||||
|
Tutorial
|
||||||
|
========
|
||||||
|
|
||||||
|
.. margin at column 75
|
||||||
|
|
||||||
|
Read this if you want to learn how to use Blueprint and never used
|
||||||
|
the XML syntax that can be read by GtkBuilder.
|
||||||
|
|
||||||
|
For compatibility with Blueprint IDE extensions, blueprint files
|
||||||
|
should end with ``.blp``.
|
||||||
|
|
||||||
|
|
||||||
|
Namespaces
|
||||||
|
----------
|
||||||
|
|
||||||
|
Blueprint needs the widget library to be imported. These include Gtk,
|
||||||
|
Libadwaita, Shumate, etc. To import a namespace, write ``using`` followed
|
||||||
|
by the library and version number.
|
||||||
|
|
||||||
|
.. code-block::
|
||||||
|
|
||||||
|
using Gtk 4.0;
|
||||||
|
using Adw 1;
|
||||||
|
|
||||||
|
The Gtk import is required in all blueprints and the minor version
|
||||||
|
number must be 0.
|
||||||
|
|
||||||
|
|
||||||
|
Comments
|
||||||
|
--------
|
||||||
|
|
||||||
|
Blueprint has inline or multi-line comments
|
||||||
|
|
||||||
|
.. code-block::
|
||||||
|
|
||||||
|
// This is an inline comment
|
||||||
|
/* This is
|
||||||
|
a multiline
|
||||||
|
comment */
|
||||||
|
|
||||||
|
Multi-line comments can't have inner multi-line comments. The compiler
|
||||||
|
will interpret the inner comment's closing token as the outer comment's
|
||||||
|
closing token. For example, the following will not compile:
|
||||||
|
|
||||||
|
.. code-block::
|
||||||
|
|
||||||
|
// Bad comment below:
|
||||||
|
/* Outer comment
|
||||||
|
/* Inner comment */
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
Widgets
|
||||||
|
-------
|
||||||
|
|
||||||
|
Create widgets in the following format:
|
||||||
|
|
||||||
|
.. code-block::
|
||||||
|
|
||||||
|
Namespace.WidgetClass {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
The Gtk namespace is implied for widgets, so you can just write the
|
||||||
|
widget class
|
||||||
|
|
||||||
|
.. code-block::
|
||||||
|
|
||||||
|
Box {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Other namespaces must be written explicitly.
|
||||||
|
|
||||||
|
.. code-block::
|
||||||
|
|
||||||
|
Adw.Leaflet {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Consult the widget library's documentation for a list of widgets.
|
||||||
|
A good place to start is
|
||||||
|
`the Gtk4 widget list <https://docs.gtk.org/gtk4/index.html>`_.
|
||||||
|
|
||||||
|
Naming Widgets
|
||||||
|
~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Widgets can be given a **name/ID** so that they can be referenced by your
|
||||||
|
program or other widgets in the blueprint.
|
||||||
|
|
||||||
|
.. code-block::
|
||||||
|
|
||||||
|
Namespace.WidgetClass widget_id {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Any time you want to use this widget as a property (more about that in the
|
||||||
|
next section) or something else, write the widget's **ID** (e.g.
|
||||||
|
``main_window``).
|
||||||
|
|
||||||
|
|
||||||
|
Properties
|
||||||
|
----------
|
||||||
|
|
||||||
|
Every widget has properties defined by their GObject class.
|
||||||
|
For example, the Libadwaita documentation lists the
|
||||||
|
`properties of the Toast class <https://gnome.pages.gitlab.gnome.org/libadwaita/doc/1.2/class.Toast.html#properties>`_.
|
||||||
|
Write properties inside the curly brackets of a widget:
|
||||||
|
|
||||||
|
.. code-block::
|
||||||
|
|
||||||
|
Namespace.WidgetClass {
|
||||||
|
property-name: value;
|
||||||
|
}
|
||||||
|
|
||||||
|
Properties values are *all lowercase* (except strings) and must end with a
|
||||||
|
semicolon (``;``).
|
||||||
|
|
||||||
|
Property Types
|
||||||
|
~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
These are the **types** of values that can be used in properties:
|
||||||
|
- Booleans: ``true``, ``false``
|
||||||
|
- Numbers: e.g. ``1``, ``1.5``, ``-2``, ``-2.5``
|
||||||
|
- Strings (single- or double-quoted): e.g. ``"a string"``, ``'another string'``
|
||||||
|
- Enums
|
||||||
|
- Widgets
|
||||||
|
|
||||||
|
Properties are **strongly typed**, so you can't use, for example, a string
|
||||||
|
for the orientation property, which requires an ``Orientation`` enum
|
||||||
|
vartiant as its value.
|
||||||
|
|
||||||
|
Enum Properties
|
||||||
|
~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
In the Gtk documentation, enum variants have long names and are
|
||||||
|
capitalized. For example, these are the
|
||||||
|
`Orientation <https://docs.gtk.org/gtk4/enum.Orientation.html>`_
|
||||||
|
enum variants:
|
||||||
|
|
||||||
|
- GTK_ORIENTATION_HORIZONTAL
|
||||||
|
- GTK_ORIENTATION_VERTICAL
|
||||||
|
|
||||||
|
In the blueprint, you would only write the *variant* part of the enum in
|
||||||
|
*lowercase*, just like you would in the XML.
|
||||||
|
|
||||||
|
.. code-block::
|
||||||
|
|
||||||
|
Box {
|
||||||
|
orientation: horizontal;
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget Properties
|
||||||
|
~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Some widgets take other widgets as properties. For example, the
|
||||||
|
``Gtk.StackSidebar`` has a stack property which takes a ``Gtk.Stack`` widget.
|
||||||
|
You can create a new widget for the value, or you can reference another
|
||||||
|
widget by its **ID**.
|
||||||
|
|
||||||
|
.. code-block::
|
||||||
|
|
||||||
|
StackSidebar {
|
||||||
|
stack: Stack { };
|
||||||
|
}
|
||||||
|
|
||||||
|
OR
|
||||||
|
|
||||||
|
.. code-block::
|
||||||
|
|
||||||
|
StackSidebar {
|
||||||
|
stack: my_stack;
|
||||||
|
}
|
||||||
|
|
||||||
|
Stack my_stack {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Note the use of a semicolon at the end of the property in both cases.
|
||||||
|
Inline widget properties are not exempt of this rule.
|
||||||
|
|
||||||
|
|
||||||
|
Property Bindings
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
If you want a widget's property to have the same value as another widget's
|
||||||
|
property (without hard-coding the value), you could ``bind`` two widgets'
|
||||||
|
properties of the same type. Bindings must reference a *source* widget by
|
||||||
|
**ID**. As long as the two properties have the same type, you can bind
|
||||||
|
properties of different names and of widgets with different widget classes.
|
||||||
|
|
||||||
|
.. code-block::
|
||||||
|
|
||||||
|
Box my_box {
|
||||||
|
halign: fill; // Source
|
||||||
|
}
|
||||||
|
|
||||||
|
Button {
|
||||||
|
valign: bind my_box.halign; // Target
|
||||||
|
}
|
||||||
|
|
||||||
|
Binding Flags
|
||||||
|
~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Modify the behavior of bindings with flags. Flags are written after the
|
||||||
|
binding. The default behavior is that the *Target*'s value will be
|
||||||
|
changed to the *Source*'s value when the binding is created and when the
|
||||||
|
*Source* value changes.
|
||||||
|
|
||||||
|
.. code-block::
|
||||||
|
|
||||||
|
Box my_box {
|
||||||
|
hexpand: true; // Source
|
||||||
|
}
|
||||||
|
|
||||||
|
Button {
|
||||||
|
vexpand: bind my_box.hexpand inverted bidirectional; // Target
|
||||||
|
}
|
||||||
|
|
||||||
|
no-sync-create
|
||||||
|
Prevent setting the *Tartget* with the *Source*'s value,
|
||||||
|
updating the target value when the *Source* value changes, not when
|
||||||
|
the binding is first created. Useful when the *Target* property has
|
||||||
|
another initial value that is not the *Source* value.
|
||||||
|
|
||||||
|
bidirectional
|
||||||
|
When either the *Source* or *Target* value is modified, the other's
|
||||||
|
value will be updated. For example, if the logic of the program
|
||||||
|
changes the Button's vexpand value to ``false``, then the Box's halign
|
||||||
|
value will also be updated to ``false``.
|
||||||
|
|
||||||
|
inverted
|
||||||
|
If the property is a boolean, the value of the bind can be negated
|
||||||
|
with this flag. For example, if the Box's hexpand property is ``true``,
|
||||||
|
the Button's vexpand property will be ``false`` in the code above.
|
||||||
|
|
||||||
|
|
||||||
|
Signals
|
||||||
|
-------
|
||||||
|
|
||||||
|
Gtk allows you to register signals in your program. This can be done by
|
||||||
|
getting the object from the GtkBuilder and connecting a handler to the
|
||||||
|
signal. Or register the handler with the application and reference it in
|
||||||
|
the blueprint.
|
||||||
|
|
||||||
|
Signals have an *event name*, a *handler* (aka callback), and optionally
|
||||||
|
some *flags*. Each widget will have a set of defined signals. Consult the
|
||||||
|
widget's documentation for a list of its signals.
|
||||||
|
|
||||||
|
To register a handler with the application, consult the documentation for
|
||||||
|
your language's bindings of Gtk.
|
||||||
|
|
||||||
|
.. code-block::
|
||||||
|
|
||||||
|
WidgetClass {
|
||||||
|
event_name => handler_name() flags;
|
||||||
|
}
|
||||||
|
|
||||||
|
.. TODO: add a list of flags and their descriptions
|
||||||
|
|
||||||
|
By default, signals in the blueprint will pass the widget that the signal
|
||||||
|
is for as an argument to the *handler*. However, you can specify the
|
||||||
|
widget that is passed to the handler by referencing its **ID** inside the
|
||||||
|
parenthesis.
|
||||||
|
|
||||||
|
.. code-block::
|
||||||
|
|
||||||
|
Label my_label {
|
||||||
|
label: "Hide me";
|
||||||
|
}
|
||||||
|
|
||||||
|
Button {
|
||||||
|
clicked => hide_widget(my_label);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Custom Widget Classes
|
||||||
|
---------------------
|
||||||
|
|
||||||
|
Some programs have custom widgets defined in their logic, so blueprint
|
||||||
|
won't know that they exist. Writing widgets not defined in the GIR will
|
||||||
|
result in an error. Prepend a custom widget with a period (``.``) to prevent the
|
||||||
|
compiler from trying to validate the widget. This is essentially saying
|
||||||
|
the widget has no *namespace*.
|
||||||
|
|
||||||
|
To register a custom widget with the application consult the documentation
|
||||||
|
for your language's bindings of Gtk.
|
||||||
|
|
||||||
|
.. code-block::
|
||||||
|
|
||||||
|
.MyCustomWidget {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Templates
|
||||||
|
---------
|
||||||
|
.. TODO
|
||||||
|
|
||||||
|
|
||||||
|
CSS Style Classes
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
.. TODO: Unsure if to group styles with widget-specific items
|
||||||
|
|
||||||
|
Widgets can be given style classes that can be used with your CSS or
|
||||||
|
`predefined styles <https://gnome.pages.gitlab.gnome.org/libadwaita/doc/1.2/style-classes.html>`_
|
||||||
|
in libraries like Libadwaita.
|
||||||
|
|
||||||
|
.. code-block::
|
||||||
|
|
||||||
|
Button {
|
||||||
|
label: "Click me";
|
||||||
|
styles ["my-style", "pill"]
|
||||||
|
}
|
||||||
|
|
||||||
|
Note the lack of a *colon* after "styles" and a *semicolon* at the end of
|
||||||
|
the line. This syntax looks like the properties syntax, but it compiles to
|
||||||
|
XML completely different from properties.
|
||||||
|
|
||||||
|
Consult your language's bindings of Gtk to use a CSS file.
|
||||||
|
|
||||||
|
Non-property Elements
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Some widgets will have elements which are not properties, but they sort
|
||||||
|
of act like properties. Most of the time they will be specific only to a
|
||||||
|
certain widget. *Styles* is one of these elements, except that styles can
|
||||||
|
be used for any widget. Similar to how every widget has styles,
|
||||||
|
``Gtk.ComboBoxText`` has *items*:
|
||||||
|
|
||||||
|
.. code-block::
|
||||||
|
|
||||||
|
Gtk.ComboBoxText {
|
||||||
|
items [
|
||||||
|
item1: "Item 1",
|
||||||
|
item2: _("Items can be translated"),
|
||||||
|
"The item ID is not required",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
See :doc:`examples <examples#widget-specific-items>` for a list of more of these
|
||||||
|
widget-specific items.
|
||||||
|
|
||||||
|
|
||||||
|
Menus
|
||||||
|
-----
|
||||||
|
|
||||||
|
Menus are usually the widgets that are placed along the top-bar of a
|
||||||
|
window, or pop up when you right-click some other widget. In Blueprint, a
|
||||||
|
``menu`` is a ``Gio.MenuModel`` that can be shown by MenuButtons or other
|
||||||
|
widgets.
|
||||||
|
|
||||||
|
In Blueprint, a ``menu`` can have *items*, *sections*, and *submenus*.
|
||||||
|
Like widgets, a ``menu`` can also be given an **ID**.
|
||||||
|
The `Menu Model section of the Gtk.PopoverMenu documentation <https://docs.gtk.org/gtk4/class.PopoverMenu.html#menu-models>`_
|
||||||
|
has complete details on the menu model.
|
||||||
|
|
||||||
|
Here is an example of a menu:
|
||||||
|
|
||||||
|
.. code-block::
|
||||||
|
|
||||||
|
menu my_menu {
|
||||||
|
section {
|
||||||
|
label: "File";
|
||||||
|
item {
|
||||||
|
label: "Open";
|
||||||
|
action: "win.open";
|
||||||
|
icon-name: "document-open-symbolic";
|
||||||
|
}
|
||||||
|
item {
|
||||||
|
label: "Save";
|
||||||
|
action: "win.save";
|
||||||
|
icon-name: "document-save-symbolic";
|
||||||
|
}
|
||||||
|
submenu {
|
||||||
|
label: "Save As";
|
||||||
|
icon-name: "document-save-as-symbolic";
|
||||||
|
item {
|
||||||
|
label: "PDF";
|
||||||
|
action: "win.save_as_pdf";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
There is a shorthand for *items*. Items require at least a label. The
|
||||||
|
action and icon-name are optional.
|
||||||
|
|
||||||
|
.. code-block::
|
||||||
|
|
||||||
|
menu {
|
||||||
|
item ( "Item 2" )
|
||||||
|
item ( "Item 2", "app.action", "icon-name" )
|
||||||
|
}
|
||||||
|
|
||||||
|
A widget that uses a ``menu`` is ``Gtk.MenuButton``. It has the *menu-model*
|
||||||
|
property, which takes a menu. Write the menu at the root of the blueprint
|
||||||
|
(meaning not inside any widgets) and reference it by **ID**.
|
||||||
|
|
||||||
|
.. code-block::
|
||||||
|
|
||||||
|
MenuButton {
|
||||||
|
menu-model: my_menu;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Child Types
|
||||||
|
-----------
|
||||||
|
|
||||||
|
Child types describe how a child widget is placed on a parent widget. For
|
||||||
|
example, HeaderBar widgets can have children placed either at the *start*
|
||||||
|
or the *end* of the HeaderBar. Child widgets of HeaderBars can have the
|
||||||
|
*start* or *end* types. Values for child types a widget can have are
|
||||||
|
defined in the widget's documentation.
|
||||||
|
|
||||||
|
Child types in blueprint are written between square brackets (``[`` ``]``) and before
|
||||||
|
the child the type is for.
|
||||||
|
|
||||||
|
The following blueprint code...
|
||||||
|
|
||||||
|
.. code-block::
|
||||||
|
|
||||||
|
HeaderBar {
|
||||||
|
[start]
|
||||||
|
Button {
|
||||||
|
label: "Button";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
\... would look like this:
|
||||||
|
|
||||||
|
.. code-block::
|
||||||
|
|
||||||
|
---------------------------
|
||||||
|
| Button |
|
||||||
|
---------------------------
|
||||||
|
|
||||||
|
And the following blueprint code...
|
||||||
|
|
||||||
|
.. code-block::
|
||||||
|
|
||||||
|
HeaderBar {
|
||||||
|
[end]
|
||||||
|
Button {
|
||||||
|
label: "Button";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
\... would look like this:
|
||||||
|
|
||||||
|
.. code-block::
|
||||||
|
|
||||||
|
---------------------------
|
||||||
|
| Button |
|
||||||
|
---------------------------
|
||||||
|
|
||||||
|
|
||||||
|
Translatable Strings
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
Mark any string as translatable using this syntax: ``_("...")``.
|
||||||
|
|
||||||
|
Two strings that are the same in English could be translated in different
|
||||||
|
ways in other languages because of different *contexts*. Translatable
|
||||||
|
strings with context look like this: ``C_("context", "...")``. An example
|
||||||
|
where a context is needed is the word "have", which in Spanish could
|
||||||
|
translate to "tener" or "haber".
|
||||||
|
|
||||||
|
.. code-block::
|
||||||
|
|
||||||
|
Label {
|
||||||
|
label: C_("1st have", "have");
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
label: C_("2nd have", "have");
|
||||||
|
}
|
||||||
|
|
||||||
|
See `translations <translations.html>`_ for more details.
|
15
tests/samples/issue_187.ui
Normal file
15
tests/samples/issue_187.ui
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<interface>
|
||||||
|
<requires lib="gtk" version="4.0"/>
|
||||||
|
<template class="GtkListItem">
|
||||||
|
<property name="child">
|
||||||
|
<object class="GtkLabel">
|
||||||
|
<binding name="label">
|
||||||
|
<lookup type="RecentObject" name="filename">
|
||||||
|
<lookup name="item">GtkListItem</lookup>
|
||||||
|
</lookup>
|
||||||
|
</binding>
|
||||||
|
</object>
|
||||||
|
</property>
|
||||||
|
</template>
|
||||||
|
</interface>
|
7
tests/samples/issue_187_dec.blp
Normal file
7
tests/samples/issue_187_dec.blp
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
using Gtk 4.0;
|
||||||
|
|
||||||
|
template ListItem {
|
||||||
|
child: Label {
|
||||||
|
label: bind template.item as <$RecentObject>.filename;
|
||||||
|
};
|
||||||
|
}
|
11
tests/samples/issue_195.blp
Normal file
11
tests/samples/issue_195.blp
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
using Gtk 4.0;
|
||||||
|
using Adw 1;
|
||||||
|
|
||||||
|
Adw.AlertDialog dialog1 {
|
||||||
|
responses [
|
||||||
|
ok: "Ok",
|
||||||
|
cancel: "Cancel",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
Button cancel {}
|
16
tests/samples/issue_195.ui
Normal file
16
tests/samples/issue_195.ui
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!--
|
||||||
|
DO NOT EDIT!
|
||||||
|
This file was @generated by blueprint-compiler. Instead, edit the
|
||||||
|
corresponding .blp file and regenerate this file with blueprint-compiler.
|
||||||
|
-->
|
||||||
|
<interface>
|
||||||
|
<requires lib="gtk" version="4.0"/>
|
||||||
|
<object class="AdwAlertDialog" id="dialog1">
|
||||||
|
<responses>
|
||||||
|
<response id="ok">Ok</response>
|
||||||
|
<response id="cancel">Cancel</response>
|
||||||
|
</responses>
|
||||||
|
</object>
|
||||||
|
<object class="GtkButton" id="cancel"></object>
|
||||||
|
</interface>
|
7
tests/samples/signal_template_object.blp
Normal file
7
tests/samples/signal_template_object.blp
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
using Gtk 4.0;
|
||||||
|
|
||||||
|
template $MyTemplate {
|
||||||
|
Button {
|
||||||
|
clicked => $my_signal_handler(template);
|
||||||
|
}
|
||||||
|
}
|
16
tests/samples/signal_template_object.ui
Normal file
16
tests/samples/signal_template_object.ui
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!--
|
||||||
|
DO NOT EDIT!
|
||||||
|
This file was @generated by blueprint-compiler. Instead, edit the
|
||||||
|
corresponding .blp file and regenerate this file with blueprint-compiler.
|
||||||
|
-->
|
||||||
|
<interface>
|
||||||
|
<requires lib="gtk" version="4.0"/>
|
||||||
|
<template class="MyTemplate">
|
||||||
|
<child>
|
||||||
|
<object class="GtkButton">
|
||||||
|
<signal name="clicked" handler="my_signal_handler" object="MyTemplate"/>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</template>
|
||||||
|
</interface>
|
|
@ -181,11 +181,7 @@ class TestSamples(unittest.TestCase):
|
||||||
|
|
||||||
def test_samples(self):
|
def test_samples(self):
|
||||||
# list the samples directory
|
# list the samples directory
|
||||||
samples = [
|
samples = [f.stem for f in Path(__file__).parent.glob("samples/*.blp")]
|
||||||
f.stem
|
|
||||||
for f in Path(__file__).parent.glob("samples/*.blp")
|
|
||||||
if not f.stem.endswith("_dec")
|
|
||||||
]
|
|
||||||
samples.sort()
|
samples.sort()
|
||||||
for sample in samples:
|
for sample in samples:
|
||||||
REQUIRE_ADW_1_4 = ["adw_breakpoint"]
|
REQUIRE_ADW_1_4 = ["adw_breakpoint"]
|
||||||
|
@ -202,6 +198,7 @@ class TestSamples(unittest.TestCase):
|
||||||
"parseable",
|
"parseable",
|
||||||
"signal",
|
"signal",
|
||||||
"signal_not_swapped",
|
"signal_not_swapped",
|
||||||
|
"signal_template_object",
|
||||||
"template",
|
"template",
|
||||||
"template_binding",
|
"template_binding",
|
||||||
"template_binding_extern",
|
"template_binding_extern",
|
||||||
|
@ -215,7 +212,7 @@ class TestSamples(unittest.TestCase):
|
||||||
]
|
]
|
||||||
|
|
||||||
# Decompiler-only tests
|
# Decompiler-only tests
|
||||||
SKIP_COMPILE = ["issue_177", "translator_comments"]
|
SKIP_COMPILE = ["issue_177", "issue_187", "translator_comments"]
|
||||||
|
|
||||||
SKIP_DECOMPILE = [
|
SKIP_DECOMPILE = [
|
||||||
# Comments are not preserved in either direction
|
# Comments are not preserved in either direction
|
||||||
|
@ -228,7 +225,7 @@ class TestSamples(unittest.TestCase):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
with self.subTest(sample):
|
with self.subTest(sample):
|
||||||
if sample not in SKIP_COMPILE:
|
if sample not in SKIP_COMPILE and not sample.endswith("_dec"):
|
||||||
self.assert_sample(sample, skip_run=sample in SKIP_RUN)
|
self.assert_sample(sample, skip_run=sample in SKIP_RUN)
|
||||||
|
|
||||||
with self.subTest("decompile/" + sample):
|
with self.subTest("decompile/" + sample):
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue