mirror of
https://gitlab.gnome.org/jwestman/blueprint-compiler.git
synced 2025-05-04 15:59:08 -04:00
Compare commits
16 commits
07defc73ed
...
ea1daba263
Author | SHA1 | Date | |
---|---|---|---|
|
ea1daba263 | ||
|
29e4a56bfc | ||
|
8c6f8760f7 | ||
|
b9f58aeab5 | ||
|
55e5095fba | ||
|
f3faf4b993 | ||
|
e1f972ef16 | ||
|
13e477aa25 | ||
|
ba8ec80456 | ||
|
0fe58ffc37 | ||
|
14d1892254 | ||
|
c74c5ac232 | ||
|
9e293a31e6 | ||
|
cce1af5f09 | ||
|
4dd55ab2aa | ||
|
52e651a168 |
42 changed files with 842 additions and 27 deletions
|
@ -177,6 +177,15 @@ def property_completer(lsp, ast_node, match_variables):
|
|||
docs=prop.doc,
|
||||
detail=prop.detail,
|
||||
)
|
||||
elif prop.type.full_name == "Gtk.Expression":
|
||||
yield Completion(
|
||||
prop_name,
|
||||
CompletionItemKind.Property,
|
||||
sort_text=f"0 {prop_name}",
|
||||
snippet=f"{prop_name}: expr $0;",
|
||||
docs=prop.doc,
|
||||
detail=prop.detail,
|
||||
)
|
||||
else:
|
||||
yield Completion(
|
||||
prop_name,
|
||||
|
|
|
@ -146,8 +146,10 @@ def format(data, tab_size=2, insert_space=True):
|
|||
is_child_type = False
|
||||
|
||||
elif str_item in CLOSING_TOKENS:
|
||||
if str_item == "]" and last_not_whitespace != ",":
|
||||
if str_item == "]" and str(last_not_whitespace) != "[":
|
||||
current_line = current_line[:-1]
|
||||
if str(last_not_whitespace) != ",":
|
||||
current_line += ","
|
||||
commit_current_line()
|
||||
current_line = "]"
|
||||
elif str(last_not_whitespace) in OPENING_TOKENS:
|
||||
|
@ -191,6 +193,9 @@ def format(data, tab_size=2, insert_space=True):
|
|||
elif prev_line_type in require_extra_newline:
|
||||
newlines = 2
|
||||
|
||||
current_line = "\n".join(
|
||||
[line.rstrip() for line in current_line.split("\n")]
|
||||
)
|
||||
commit_current_line(LineType.COMMENT, newlines_before=newlines)
|
||||
|
||||
else: # pragma: no cover
|
||||
|
|
|
@ -41,6 +41,7 @@ from .types import ClassName
|
|||
from .ui import UI
|
||||
from .values import (
|
||||
ArrayValue,
|
||||
ExprValue,
|
||||
Flag,
|
||||
Flags,
|
||||
IdentLiteral,
|
||||
|
|
|
@ -79,3 +79,9 @@ class ScopeCtx:
|
|||
for child in node.children:
|
||||
if child.context[ScopeCtx] is self:
|
||||
yield from self._iter_recursive(child)
|
||||
|
||||
|
||||
@dataclass
|
||||
class ExprValueCtx:
|
||||
"""Indicates that the context is an expression literal, where the
|
||||
"item" keyword may be used."""
|
||||
|
|
|
@ -81,6 +81,16 @@ class LiteralExpr(ExprBase):
|
|||
or self.root.is_legacy_template(self.literal.value.ident)
|
||||
)
|
||||
|
||||
@property
|
||||
def is_this(self) -> bool:
|
||||
from .values import IdentLiteral
|
||||
|
||||
return (
|
||||
not self.is_object
|
||||
and isinstance(self.literal.value, IdentLiteral)
|
||||
and self.literal.value.ident == "item"
|
||||
)
|
||||
|
||||
@property
|
||||
def literal(self):
|
||||
from .values import Literal
|
||||
|
@ -91,6 +101,15 @@ class LiteralExpr(ExprBase):
|
|||
def type(self) -> T.Optional[GirType]:
|
||||
return self.literal.value.type
|
||||
|
||||
@validate()
|
||||
def item_validations(self):
|
||||
if self.is_this:
|
||||
if not isinstance(self.rhs, CastExpr):
|
||||
raise CompileError('"item" must be cast to its object type')
|
||||
|
||||
if not isinstance(self.rhs.rhs, LookupOp):
|
||||
raise CompileError('"item" can only be used for looking up properties')
|
||||
|
||||
|
||||
class LookupOp(InfixExpr):
|
||||
grammar = [".", UseIdent("property")]
|
||||
|
@ -285,6 +304,9 @@ expr.children = [
|
|||
def decompile_lookup(
|
||||
ctx: DecompileCtx, gir: gir.GirContext, cdata: str, name: str, type: str
|
||||
):
|
||||
if ctx.parent_node is not None and ctx.parent_node.tag == "property":
|
||||
ctx.print("expr ")
|
||||
|
||||
if t := ctx.type_by_cname(type):
|
||||
type = decompile.full_name(t)
|
||||
else:
|
||||
|
@ -304,6 +326,8 @@ def decompile_lookup(
|
|||
if constant is not None:
|
||||
if constant == ctx.template_class:
|
||||
ctx.print("template." + name)
|
||||
elif constant == "":
|
||||
ctx.print("item as <" + type + ">." + name)
|
||||
else:
|
||||
ctx.print(constant + "." + name)
|
||||
return
|
||||
|
@ -318,6 +342,9 @@ def decompile_lookup(
|
|||
def decompile_constant(
|
||||
ctx: DecompileCtx, gir: gir.GirContext, cdata: str, type: T.Optional[str] = None
|
||||
):
|
||||
if ctx.parent_node is not None and ctx.parent_node.tag == "property":
|
||||
ctx.print("expr ")
|
||||
|
||||
if type is None:
|
||||
if cdata == ctx.template_class:
|
||||
ctx.print("template")
|
||||
|
@ -330,6 +357,9 @@ def decompile_constant(
|
|||
|
||||
@decompiler("closure", skip_children=True)
|
||||
def decompile_closure(ctx: DecompileCtx, gir: gir.GirContext, function: str, type: str):
|
||||
if ctx.parent_node is not None and ctx.parent_node.tag == "property":
|
||||
ctx.print("expr ")
|
||||
|
||||
if t := ctx.type_by_cname(type):
|
||||
type = decompile.full_name(t)
|
||||
else:
|
||||
|
|
|
@ -28,7 +28,18 @@ from .common import *
|
|||
from .response_id import ExtResponse
|
||||
from .types import ClassName, ConcreteClassName
|
||||
|
||||
RESERVED_IDS = {"this", "self", "template", "true", "false", "null", "none"}
|
||||
RESERVED_IDS = {
|
||||
"this",
|
||||
"self",
|
||||
"template",
|
||||
"true",
|
||||
"false",
|
||||
"null",
|
||||
"none",
|
||||
"item",
|
||||
"expr",
|
||||
"typeof",
|
||||
}
|
||||
|
||||
|
||||
class ObjectContent(AstNode):
|
||||
|
|
|
@ -21,13 +21,12 @@
|
|||
from .binding import Binding
|
||||
from .common import *
|
||||
from .contexts import ValueTypeCtx
|
||||
from .gtkbuilder_template import Template
|
||||
from .values import ArrayValue, ObjectValue, Value
|
||||
from .values import ArrayValue, ExprValue, ObjectValue, Value
|
||||
|
||||
|
||||
class Property(AstNode):
|
||||
grammar = Statement(
|
||||
UseIdent("name"), ":", AnyOf(Binding, ObjectValue, Value, ArrayValue)
|
||||
UseIdent("name"), ":", AnyOf(Binding, ExprValue, ObjectValue, Value, ArrayValue)
|
||||
)
|
||||
|
||||
@property
|
||||
|
@ -35,7 +34,7 @@ class Property(AstNode):
|
|||
return self.tokens["name"]
|
||||
|
||||
@property
|
||||
def value(self) -> T.Union[Binding, ObjectValue, Value, ArrayValue]:
|
||||
def value(self) -> T.Union[Binding, ExprValue, ObjectValue, Value, ArrayValue]:
|
||||
return self.children[0]
|
||||
|
||||
@property
|
||||
|
|
|
@ -23,7 +23,8 @@ from blueprintcompiler.gir import ArrayType
|
|||
from blueprintcompiler.lsp_utils import SemanticToken
|
||||
|
||||
from .common import *
|
||||
from .contexts import ScopeCtx, ValueTypeCtx
|
||||
from .contexts import ExprValueCtx, ScopeCtx, ValueTypeCtx
|
||||
from .expression import Expression
|
||||
from .gobject_object import Object
|
||||
from .types import TypeName
|
||||
|
||||
|
@ -319,7 +320,12 @@ class IdentLiteral(AstNode):
|
|||
if self.ident == "null":
|
||||
if not self.context[ValueTypeCtx].allow_null:
|
||||
raise CompileError("null is not permitted here")
|
||||
else:
|
||||
elif self.ident == "item":
|
||||
if not self.context[ExprValueCtx]:
|
||||
raise CompileError(
|
||||
'"item" can only be used in an expression literal'
|
||||
)
|
||||
elif self.ident not in ["true", "false"]:
|
||||
raise CompileError(
|
||||
f"Could not find object with ID {self.ident}",
|
||||
did_you_mean=(
|
||||
|
@ -407,6 +413,35 @@ class ObjectValue(AstNode):
|
|||
)
|
||||
|
||||
|
||||
class ExprValue(AstNode):
|
||||
grammar = [Keyword("expr"), Expression]
|
||||
|
||||
@property
|
||||
def expression(self) -> Expression:
|
||||
return self.children[Expression][0]
|
||||
|
||||
@validate("expr")
|
||||
def validate_for_type(self) -> None:
|
||||
expected_type = self.parent.context[ValueTypeCtx].value_type
|
||||
expr_type = self.root.gir.get_type("Expression", "Gtk")
|
||||
if expected_type is not None and not expected_type.assignable_to(expr_type):
|
||||
raise CompileError(
|
||||
f"Cannot convert Gtk.Expression to {expected_type.full_name}"
|
||||
)
|
||||
|
||||
@docs("expr")
|
||||
def ref_docs(self):
|
||||
return get_docs_section("Syntax ExprValue")
|
||||
|
||||
@context(ExprValueCtx)
|
||||
def expr_literal(self):
|
||||
return ExprValueCtx()
|
||||
|
||||
@context(ValueTypeCtx)
|
||||
def value_type(self):
|
||||
return ValueTypeCtx(None, must_infer_type=True)
|
||||
|
||||
|
||||
class Value(AstNode):
|
||||
grammar = AnyOf(Translated, Flags, Literal)
|
||||
|
||||
|
@ -452,6 +487,14 @@ class ArrayValue(AstNode):
|
|||
range=quoted_literal.range,
|
||||
)
|
||||
)
|
||||
elif isinstance(value.child, Translated):
|
||||
errors.append(
|
||||
CompileError(
|
||||
"Arrays can't contain translated strings",
|
||||
range=value.child.range,
|
||||
)
|
||||
)
|
||||
|
||||
if len(errors) > 0:
|
||||
raise MultipleErrors(errors)
|
||||
|
||||
|
|
|
@ -118,6 +118,7 @@ class LanguageServer:
|
|||
self.client_capabilities = {}
|
||||
self.client_supports_completion_choice = False
|
||||
self._open_files: T.Dict[str, OpenFile] = {}
|
||||
self._exited = False
|
||||
|
||||
def run(self):
|
||||
# Read <doc> tags from gir files. During normal compilation these are
|
||||
|
@ -125,7 +126,7 @@ class LanguageServer:
|
|||
xml_reader.PARSE_GIR.add("doc")
|
||||
|
||||
try:
|
||||
while True:
|
||||
while not self._exited:
|
||||
line = ""
|
||||
content_len = -1
|
||||
while content_len == -1 or (line != "\n" and line != "\r\n"):
|
||||
|
@ -221,6 +222,14 @@ class LanguageServer:
|
|||
},
|
||||
)
|
||||
|
||||
@command("shutdown")
|
||||
def shutdown(self, id, params):
|
||||
self._send_response(id, None)
|
||||
|
||||
@command("exit")
|
||||
def exit(self, id, params):
|
||||
self._exited = True
|
||||
|
||||
@command("textDocument/didOpen")
|
||||
def didOpen(self, id, params):
|
||||
doc = params.get("textDocument")
|
||||
|
|
|
@ -134,6 +134,11 @@ class XmlOutput(OutputFormat):
|
|||
self._emit_expression(value.expression, xml)
|
||||
xml.end_tag()
|
||||
|
||||
elif isinstance(value, ExprValue):
|
||||
xml.start_tag("property", **props)
|
||||
self._emit_expression(value.expression, xml)
|
||||
xml.end_tag()
|
||||
|
||||
elif isinstance(value, ObjectValue):
|
||||
xml.start_tag("property", **props)
|
||||
self._emit_object(value.object, xml)
|
||||
|
@ -218,12 +223,6 @@ class XmlOutput(OutputFormat):
|
|||
xml.put_text(
|
||||
"|".join([str(flag.value or flag.name) for flag in value.child.flags])
|
||||
)
|
||||
elif isinstance(value.child, Translated):
|
||||
raise CompilerBugError("translated values must be handled in the parent")
|
||||
elif isinstance(value.child, TypeLiteral):
|
||||
xml.put_text(value.child.type_name.glib_type_name)
|
||||
elif isinstance(value.child, ObjectValue):
|
||||
self._emit_object(value.child.object, xml)
|
||||
else:
|
||||
raise CompilerBugError()
|
||||
|
||||
|
@ -245,6 +244,9 @@ class XmlOutput(OutputFormat):
|
|||
raise CompilerBugError()
|
||||
|
||||
def _emit_literal_expr(self, expr: LiteralExpr, xml: XmlEmitter):
|
||||
if expr.is_this:
|
||||
return
|
||||
|
||||
if expr.is_object:
|
||||
xml.start_tag("constant")
|
||||
else:
|
||||
|
|
|
@ -42,8 +42,8 @@ Expressions are composed of property lookups and/or closures. Property lookups a
|
|||
|
||||
.. _Syntax LookupExpression:
|
||||
|
||||
Lookup Expressions
|
||||
------------------
|
||||
Lookups
|
||||
-------
|
||||
|
||||
.. rst-class:: grammar-block
|
||||
|
||||
|
@ -56,8 +56,8 @@ The type of a property expression is the type of the property it refers to.
|
|||
|
||||
.. _Syntax ClosureExpression:
|
||||
|
||||
Closure Expressions
|
||||
-------------------
|
||||
Closures
|
||||
--------
|
||||
|
||||
.. rst-class:: grammar-block
|
||||
|
||||
|
@ -72,8 +72,8 @@ Blueprint doesn't know the closure's return type, so closure expressions must be
|
|||
|
||||
.. _Syntax CastExpression:
|
||||
|
||||
Cast Expressions
|
||||
----------------
|
||||
Casts
|
||||
-----
|
||||
|
||||
.. rst-class:: grammar-block
|
||||
|
||||
|
@ -81,7 +81,32 @@ Cast Expressions
|
|||
|
||||
Cast expressions allow Blueprint to know the type of an expression when it can't otherwise determine it. This is necessary for closures and for properties of application-defined types.
|
||||
|
||||
Example
|
||||
~~~~~~~
|
||||
|
||||
.. code-block:: blueprint
|
||||
|
||||
// Cast the result of the closure so blueprint knows it's a string
|
||||
label: bind $my_closure() as <string>
|
||||
label: bind $format_bytes(template.file-size) as <string>
|
||||
|
||||
.. _Syntax ExprValue:
|
||||
|
||||
Expression Values
|
||||
-----------------
|
||||
|
||||
.. rst-class:: grammar-block
|
||||
|
||||
ExprValue = 'expr' :ref:`Expression<Syntax Expression>`
|
||||
|
||||
Some APIs take *an expression itself*--not its result--as a property value. For example, `Gtk.BoolFilter <https://docs.gtk.org/gtk4/class.BoolFilter.html>`_ has an ``expression`` property of type `Gtk.Expression <https://docs.gtk.org/gtk4/class.Expression.html>`_. This expression is evaluated for every item in a list model to determine whether the item should be filtered.
|
||||
|
||||
To define an expression for such a property, use ``expr`` instead of ``bind``. Inside the expression, you can use the ``item`` keyword to refer to the item being evaluated. You must cast the item to the correct type using the ``as`` keyword, and you can only use ``item`` in a property lookup--you may not pass it to a closure.
|
||||
|
||||
Example
|
||||
~~~~~~~
|
||||
|
||||
.. code-block:: blueprint
|
||||
|
||||
BoolFilter {
|
||||
expression: expr item as <$UserAccount>.active;
|
||||
}
|
||||
|
|
|
@ -58,7 +58,7 @@ Properties
|
|||
|
||||
.. rst-class:: grammar-block
|
||||
|
||||
Property = <name::ref:`IDENT<Syntax IDENT>`> ':' ( :ref:`Binding<Syntax Binding>` | :ref:`ObjectValue<Syntax ObjectValue>` | :ref:`Value<Syntax Value>` ) ';'
|
||||
Property = <name::ref:`IDENT<Syntax IDENT>`> ':' ( :ref:`Binding<Syntax Binding>` | :ref:`ExprValue<Syntax ExprValue>` | :ref:`ObjectValue<Syntax ObjectValue>` | :ref:`Value<Syntax Value>` ) ';'
|
||||
|
||||
Properties specify the details of each object, like a label's text, an image's icon name, or the margins on a container.
|
||||
|
||||
|
|
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.
|
|
@ -1,2 +1,4 @@
|
|||
using Gtk 4.0;
|
||||
//comment
|
||||
//comment
|
||||
// Trailing whitespace:
|
||||
//
|
||||
|
|
|
@ -1,2 +1,4 @@
|
|||
using Gtk 4.0;
|
||||
// comment
|
||||
// Trailing whitespace:
|
||||
//
|
||||
|
|
21
tests/formatting/lists_in.blp
Normal file
21
tests/formatting/lists_in.blp
Normal file
|
@ -0,0 +1,21 @@
|
|||
using Gtk 4.0;
|
||||
|
||||
Box {
|
||||
styles []
|
||||
}
|
||||
|
||||
Box {
|
||||
styles ["a"]
|
||||
}
|
||||
|
||||
Box {
|
||||
styles ["a",]
|
||||
}
|
||||
|
||||
Box {
|
||||
styles ["a", "b"]
|
||||
}
|
||||
|
||||
Box {
|
||||
styles ["a", "b",]
|
||||
}
|
31
tests/formatting/lists_out.blp
Normal file
31
tests/formatting/lists_out.blp
Normal file
|
@ -0,0 +1,31 @@
|
|||
using Gtk 4.0;
|
||||
|
||||
Box {
|
||||
styles []
|
||||
}
|
||||
|
||||
Box {
|
||||
styles [
|
||||
"a",
|
||||
]
|
||||
}
|
||||
|
||||
Box {
|
||||
styles [
|
||||
"a",
|
||||
]
|
||||
}
|
||||
|
||||
Box {
|
||||
styles [
|
||||
"a",
|
||||
"b",
|
||||
]
|
||||
}
|
||||
|
||||
Box {
|
||||
styles [
|
||||
"a",
|
||||
"b",
|
||||
]
|
||||
}
|
|
@ -11,7 +11,7 @@ Overlay {
|
|||
notify::icon-name => $on_icon_name_changed(label) swapped;
|
||||
|
||||
styles [
|
||||
"destructive"
|
||||
"destructive",
|
||||
]
|
||||
}
|
||||
|
||||
|
|
5
tests/sample_errors/expr_item_not_cast.blp
Normal file
5
tests/sample_errors/expr_item_not_cast.blp
Normal file
|
@ -0,0 +1,5 @@
|
|||
using Gtk 4.0;
|
||||
|
||||
BoolFilter {
|
||||
expression: expr item.visible;
|
||||
}
|
1
tests/sample_errors/expr_item_not_cast.err
Normal file
1
tests/sample_errors/expr_item_not_cast.err
Normal file
|
@ -0,0 +1 @@
|
|||
4,20,4,"item" must be cast to its object type
|
5
tests/sample_errors/expr_value_assignment.blp
Normal file
5
tests/sample_errors/expr_value_assignment.blp
Normal file
|
@ -0,0 +1,5 @@
|
|||
using Gtk 4.0;
|
||||
|
||||
Label {
|
||||
label: expr 1;
|
||||
}
|
1
tests/sample_errors/expr_value_assignment.err
Normal file
1
tests/sample_errors/expr_value_assignment.err
Normal file
|
@ -0,0 +1 @@
|
|||
4,10,4,Cannot convert Gtk.Expression to string
|
5
tests/sample_errors/expr_value_closure_arg.blp
Normal file
5
tests/sample_errors/expr_value_closure_arg.blp
Normal file
|
@ -0,0 +1,5 @@
|
|||
using Gtk 4.0;
|
||||
|
||||
BoolFilter {
|
||||
expression: expr $closure(item as <Entry>) as <bool>;
|
||||
}
|
1
tests/sample_errors/expr_value_closure_arg.err
Normal file
1
tests/sample_errors/expr_value_closure_arg.err
Normal file
|
@ -0,0 +1 @@
|
|||
4,29,4,"item" can only be used for looking up properties
|
5
tests/sample_errors/expr_value_item.blp
Normal file
5
tests/sample_errors/expr_value_item.blp
Normal file
|
@ -0,0 +1,5 @@
|
|||
using Gtk 4.0;
|
||||
|
||||
BoolFilter {
|
||||
expression: expr item as <Label>;
|
||||
}
|
1
tests/sample_errors/expr_value_item.err
Normal file
1
tests/sample_errors/expr_value_item.err
Normal file
|
@ -0,0 +1 @@
|
|||
4,20,4,"item" can only be used for looking up properties
|
7
tests/sample_errors/translated_string_array.blp
Normal file
7
tests/sample_errors/translated_string_array.blp
Normal file
|
@ -0,0 +1,7 @@
|
|||
using Gtk 4.0;
|
||||
|
||||
StringList {
|
||||
strings: [
|
||||
_("Test")
|
||||
];
|
||||
}
|
1
tests/sample_errors/translated_string_array.err
Normal file
1
tests/sample_errors/translated_string_array.err
Normal file
|
@ -0,0 +1 @@
|
|||
5,5,9,Arrays can't contain translated strings
|
9
tests/samples/bind_expr_prop.blp
Normal file
9
tests/samples/bind_expr_prop.blp
Normal file
|
@ -0,0 +1,9 @@
|
|||
using Gtk 4.0;
|
||||
|
||||
BoolFilter filter1 {
|
||||
expression: expr true;
|
||||
}
|
||||
|
||||
BoolFilter filter2 {
|
||||
expression: bind filter1.expression;
|
||||
}
|
17
tests/samples/bind_expr_prop.ui
Normal file
17
tests/samples/bind_expr_prop.ui
Normal file
|
@ -0,0 +1,17 @@
|
|||
<?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="GtkBoolFilter" id="filter1">
|
||||
<property name="expression">
|
||||
<constant type="gboolean">true</constant>
|
||||
</property>
|
||||
</object>
|
||||
<object class="GtkBoolFilter" id="filter2">
|
||||
<property name="expression" bind-source="filter1" bind-property="expression" bind-flags="sync-create"/>
|
||||
</object>
|
||||
</interface>
|
5
tests/samples/bind_literal.blp
Normal file
5
tests/samples/bind_literal.blp
Normal file
|
@ -0,0 +1,5 @@
|
|||
using Gtk 4.0;
|
||||
|
||||
Label {
|
||||
label: bind "Hello, world!";
|
||||
}
|
14
tests/samples/bind_literal.ui
Normal file
14
tests/samples/bind_literal.ui
Normal file
|
@ -0,0 +1,14 @@
|
|||
<?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="GtkLabel">
|
||||
<binding name="label">
|
||||
<constant type="gchararray">Hello, world!</constant>
|
||||
</binding>
|
||||
</object>
|
||||
</interface>
|
5
tests/samples/expr_value.blp
Normal file
5
tests/samples/expr_value.blp
Normal file
|
@ -0,0 +1,5 @@
|
|||
using Gtk 4.0;
|
||||
|
||||
BoolFilter {
|
||||
expression: expr item as <Entry>.visible;
|
||||
}
|
14
tests/samples/expr_value.ui
Normal file
14
tests/samples/expr_value.ui
Normal file
|
@ -0,0 +1,14 @@
|
|||
<?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="GtkBoolFilter">
|
||||
<property name="expression">
|
||||
<lookup name="visible" type="GtkEntry"></lookup>
|
||||
</property>
|
||||
</object>
|
||||
</interface>
|
5
tests/samples/expr_value_closure.blp
Normal file
5
tests/samples/expr_value_closure.blp
Normal file
|
@ -0,0 +1,5 @@
|
|||
using Gtk 4.0;
|
||||
|
||||
BoolFilter {
|
||||
expression: expr $closure(item as <Entry>.visible) as <bool>;
|
||||
}
|
16
tests/samples/expr_value_closure.ui
Normal file
16
tests/samples/expr_value_closure.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="GtkBoolFilter">
|
||||
<property name="expression">
|
||||
<closure function="closure" type="gboolean">
|
||||
<lookup name="visible" type="GtkEntry"></lookup>
|
||||
</closure>
|
||||
</property>
|
||||
</object>
|
||||
</interface>
|
5
tests/samples/expr_value_literal.blp
Normal file
5
tests/samples/expr_value_literal.blp
Normal file
|
@ -0,0 +1,5 @@
|
|||
using Gtk 4.0;
|
||||
|
||||
BoolFilter {
|
||||
expression: expr true;
|
||||
}
|
14
tests/samples/expr_value_literal.ui
Normal file
14
tests/samples/expr_value_literal.ui
Normal file
|
@ -0,0 +1,14 @@
|
|||
<?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="GtkBoolFilter">
|
||||
<property name="expression">
|
||||
<constant type="gboolean">true</constant>
|
||||
</property>
|
||||
</object>
|
||||
</interface>
|
|
@ -5,6 +5,6 @@ AboutDialog about {
|
|||
|
||||
authors: [
|
||||
"Jane doe <jane-doe@email.com>",
|
||||
"Jhonny D <jd@email.com>"
|
||||
"Jhonny D <jd@email.com>",
|
||||
];
|
||||
}
|
||||
|
|
|
@ -3,6 +3,6 @@ using Gtk 4.0;
|
|||
Label {
|
||||
styles [
|
||||
"class-1",
|
||||
"class-2"
|
||||
"class-2",
|
||||
]
|
||||
}
|
||||
|
|
|
@ -47,3 +47,4 @@ class TestFormatter(unittest.TestCase):
|
|||
self.assert_format_test("correct1.blp", "correct1.blp")
|
||||
self.assert_format_test("string_in.blp", "string_out.blp")
|
||||
self.assert_format_test("comment_in.blp", "comment_out.blp")
|
||||
self.assert_format_test("lists_in.blp", "lists_out.blp")
|
||||
|
|
|
@ -198,6 +198,7 @@ class TestSamples(unittest.TestCase):
|
|||
"adw_breakpoint_template",
|
||||
"expr_closure",
|
||||
"expr_closure_args",
|
||||
"expr_value_closure",
|
||||
"parseable",
|
||||
"signal",
|
||||
"signal_not_swapped",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue