mirror of
https://github.com/lvgl/lvgl.git
synced 2026-06-02 17:47:28 +08:00
docs(data_binding): move the data binding docs to to the given widget's page
This commit is contained in:
@@ -361,13 +361,9 @@ integer value:
|
|||||||
|
|
||||||
- flag (or OR-ed combination of flags) from from the ``LV_OBJ_FLAG_...`` enumeration values;
|
- flag (or OR-ed combination of flags) from from the ``LV_OBJ_FLAG_...`` enumeration values;
|
||||||
- state (or OR-ed combination of states) from the ``LV_STATE_...`` enumeration values;
|
- state (or OR-ed combination of states) from the ``LV_STATE_...`` enumeration values;
|
||||||
- text value for
|
- text and/or integer values for
|
||||||
|
|
||||||
- Label
|
- Label
|
||||||
- Span;
|
- Span;
|
||||||
|
|
||||||
- integer value for these Widget types:
|
|
||||||
|
|
||||||
- Arc
|
- Arc
|
||||||
- Drop-Down
|
- Drop-Down
|
||||||
- Roller
|
- Roller
|
||||||
@@ -457,141 +453,11 @@ Subject's value to be set to ``1`` or ``0`` respectively.
|
|||||||
|
|
||||||
- :cpp:expr:`lv_obj_bind_checked(widget, &subject)`
|
- :cpp:expr:`lv_obj_bind_checked(widget, &subject)`
|
||||||
|
|
||||||
|
Specific Widget Types
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
Label Widgets
|
To learn how to bind subjects to Arcs, Labels, Slider, etc. visit the "Data binding"
|
||||||
~~~~~~~~~~~~~
|
section of the given widget's documentation. For example: :ref:`Data binding for lv_label <lv_label_data_binding>`.
|
||||||
|
|
||||||
.. |deg| unicode:: U+000B0 .. DEGREE SIGN
|
|
||||||
|
|
||||||
This method of subscribing to an integer Subject affects a Label Widget's
|
|
||||||
``text``. The Subject can be an STRING, POINTER or INTEGER type.
|
|
||||||
|
|
||||||
When the subscribing occurs, and each time the Subject's value is changed thereafter,
|
|
||||||
the Subject's value is used to update the Label's text as follows:
|
|
||||||
|
|
||||||
:string Subject: Subject's string is used to directly update the Label's text.
|
|
||||||
|
|
||||||
:pointer Subject: If NULL is passed as the ``format_string`` argument when
|
|
||||||
subscribing, the Subject's pointer value is assumed to point to a
|
|
||||||
NUL-terminated string. and is used to directly update the Label's
|
|
||||||
text. See :ref:`observer_format_string` for other options.
|
|
||||||
|
|
||||||
:integer Subject: Subject's integer value is used with the ``format_string`` argument.
|
|
||||||
See See :ref:`observer_format_string` for details.
|
|
||||||
|
|
||||||
Note that this is a one-way binding (Subject ===> Widget).
|
|
||||||
|
|
||||||
- :cpp:expr:`lv_label_bind_text(label, &subject, format_string)`
|
|
||||||
|
|
||||||
.. _observer_format_string:
|
|
||||||
|
|
||||||
The ``format_string`` Argument
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
The ``format_string`` argument is optional and if provided, must contain exactly 1
|
|
||||||
printf-like format specifier and be one of the following:
|
|
||||||
|
|
||||||
:string or pointer Subject: "%s" to format the new pointer value as a string or "%p"
|
|
||||||
to format the pointer as a pointer (typically the
|
|
||||||
pointer's address value is spelled out with 4, 8 or 16
|
|
||||||
hexadecimal characters depending on the platform).
|
|
||||||
|
|
||||||
:integer Subject: "%d" format specifier (``"%" PRIdxx`` --- a
|
|
||||||
cross-platform equivalent where ``xx`` can be ``8``,
|
|
||||||
``16``, ``32`` or ``64``, depending on the platform).
|
|
||||||
|
|
||||||
:float Subject: "%f" format specifier, or e.g. "%0.2f" to display two digits after the decimal point.
|
|
||||||
|
|
||||||
|
|
||||||
If ``NULL`` is passed for the ``format_string`` argument:
|
|
||||||
|
|
||||||
:string or pointer Subject: Updates expect the pointer to point to a NUL-terminated string.
|
|
||||||
:integer Subject: The Label will simply display the number. Equivalent to "%d".
|
|
||||||
:float Subject: The Label will display the value with "%0.1f" format string.
|
|
||||||
|
|
||||||
**Example:** "%d |deg|\C"
|
|
||||||
|
|
||||||
|
|
||||||
Spangroup's Span
|
|
||||||
~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
Very similar to Label-text binding, a Span's text can be bound to a subject as well.
|
|
||||||
|
|
||||||
The only difference is that in the bind function both the Spangroup and the Span need to be specified:
|
|
||||||
|
|
||||||
:cpp:expr:`lv_spangroup_bind_span_text(spangroup, span1, &subject, format_string)`
|
|
||||||
|
|
||||||
Note that before calling :cpp:expr:`lv_spangroup_delete_span` :cpp:expr:`lv_observer_remove`
|
|
||||||
needs to be called manually as LVGL can't remove the binding automatically.
|
|
||||||
|
|
||||||
Arc Widgets
|
|
||||||
~~~~~~~~~~~
|
|
||||||
|
|
||||||
This method of subscribing to an integer Subject affects an Arc Widget's integer
|
|
||||||
value directly. Note that this is a two-way binding (Subject <===> Widget) so an end
|
|
||||||
user's direct interaction with the Arc Widget updates the Subject's value and vice
|
|
||||||
versa. (Requires :c:macro:`LV_USE_ARC` to be configured to ``1``.)
|
|
||||||
|
|
||||||
It support integer and float subjects.
|
|
||||||
|
|
||||||
|
|
||||||
- :cpp:expr:`lv_arc_bind_value(arc, &subject)`
|
|
||||||
|
|
||||||
|
|
||||||
Slider Widgets
|
|
||||||
~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
This method of subscribing to an integer Subject affects a Slider Widget's integer
|
|
||||||
value directly. Note that this is a two-way binding (Subject <===> Widget) so an end
|
|
||||||
user's direct interaction with the Slider Widget updates the Subject's value and vice
|
|
||||||
versa. (Requires :c:macro:`LV_USE_SLIDER` to be configured to ``1``.)
|
|
||||||
|
|
||||||
It support integer and float subjects.
|
|
||||||
|
|
||||||
- :cpp:expr:`lv_slider_bind_value(slider, &subject)`
|
|
||||||
|
|
||||||
|
|
||||||
Roller Widgets
|
|
||||||
~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
This method of subscribing to an integer Subject affects a Roller Widget's integer
|
|
||||||
value directly. Note that this is a two-way binding (Subject <===> Widget) so an end
|
|
||||||
user's direct interaction with the Slider Widget updates the Subject's value and vice
|
|
||||||
versa. (Requires :c:macro:`LV_USE_ROLLER` to be configured to ``1``.)
|
|
||||||
|
|
||||||
It support only integer subjects.
|
|
||||||
|
|
||||||
- :cpp:expr:`lv_roller_bind_value(roller, &subject)`
|
|
||||||
|
|
||||||
|
|
||||||
Drop-Down Widgets
|
|
||||||
~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
This method of subscribing to an integer Subject affects a Drop-Down Widget's integer
|
|
||||||
value directly. Note that this is a two-way binding (Subject <===> Widget) so an end
|
|
||||||
user's direct interaction with the Drop-Down Widget updates the Subject's value and
|
|
||||||
vice versa. (Requires :c:macro:`LV_USE_DROPDOWN` to be configured to ``1``.)
|
|
||||||
|
|
||||||
It support only integer subjects.
|
|
||||||
|
|
||||||
- :cpp:expr:`lv_dropdown_bind_value(dropdown, &subject)`
|
|
||||||
|
|
||||||
Scale's Section
|
|
||||||
~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
This method of subscribing to an integer Subject affects a Section of a Scale Widget's integer
|
|
||||||
minimum or maximum values directly. Note that this is a one-way binding (Subject ==> Widget)
|
|
||||||
as the Scale Section's boundaries are not interactive.
|
|
||||||
(Requires :c:macro:`LV_USE_SCALE` to be configured to ``1``.)
|
|
||||||
|
|
||||||
It supports only integer subjects.
|
|
||||||
|
|
||||||
- :cpp:expr:`lv_scale_bind_section_min_value(scale, section1, &subject)`
|
|
||||||
- :cpp:expr:`lv_scale_bind_section_max_value(scale, section1, &subject)`
|
|
||||||
|
|
||||||
|
|
||||||
.. _change_subject_on_event:
|
|
||||||
|
|
||||||
|
|
||||||
Change Subject on Event
|
Change Subject on Event
|
||||||
-----------------------
|
-----------------------
|
||||||
|
|||||||
@@ -141,6 +141,22 @@ used to rotate the Widget to the current value of the Arc.
|
|||||||
A typical use case is to call these functions in the ``VALUE_CHANGED``
|
A typical use case is to call these functions in the ``VALUE_CHANGED``
|
||||||
event of the Arc.
|
event of the Arc.
|
||||||
|
|
||||||
|
Data binding
|
||||||
|
------------
|
||||||
|
|
||||||
|
To get familiar with observers, subjects, and data bindings in general visit the
|
||||||
|
:ref:`Observer <observer_how_to_use>` page.
|
||||||
|
|
||||||
|
This method of subscribing to an integer Subject affects an Arc Widget's integer
|
||||||
|
value directly. Note that this is a two-way binding (Subject <===> Widget), so an end
|
||||||
|
user's direct interaction with the Arc Widget updates the Subject's value and vice
|
||||||
|
versa.
|
||||||
|
|
||||||
|
It support integer and float subjects.
|
||||||
|
|
||||||
|
|
||||||
|
- :cpp:expr:`lv_arc_bind_value(arc, &subject)`
|
||||||
|
|
||||||
|
|
||||||
.. _lv_arc_events:
|
.. _lv_arc_events:
|
||||||
|
|
||||||
|
|||||||
@@ -132,6 +132,21 @@ To programmatically open or close the Drop-Down List use
|
|||||||
:cpp:expr:`lv_dropdown_open(dropdown)` or :cpp:expr:`lv_dropdown_close(dropdown)`.
|
:cpp:expr:`lv_dropdown_open(dropdown)` or :cpp:expr:`lv_dropdown_close(dropdown)`.
|
||||||
|
|
||||||
|
|
||||||
|
Data binding
|
||||||
|
------------
|
||||||
|
|
||||||
|
To get familiar with observers, subjects, and data bindings in general visit the
|
||||||
|
:ref:`Observer <observer_how_to_use>` page.
|
||||||
|
|
||||||
|
This method of subscribing to an integer Subject affects a Drop-Down Widget's integer
|
||||||
|
value directly. Note that this is a two-way binding (Subject <===> Widget) so an end
|
||||||
|
user's direct interaction with the Drop-Down Widget updates the Subject's value and
|
||||||
|
vice versa.
|
||||||
|
|
||||||
|
It support only integer subjects.
|
||||||
|
|
||||||
|
- :cpp:expr:`lv_dropdown_bind_value(dropdown, &subject)`
|
||||||
|
|
||||||
|
|
||||||
.. _lv_dropdown_events:
|
.. _lv_dropdown_events:
|
||||||
|
|
||||||
|
|||||||
@@ -194,7 +194,19 @@ To automatically scale or tile the image, pass one of these ``align`` values:
|
|||||||
- :cpp:enumerator:`LV_IMAGE_ALIGN_CONTAIN` The image keeps its aspect ratio, but is resized to the maximum size that fits within the Widget's area.
|
- :cpp:enumerator:`LV_IMAGE_ALIGN_CONTAIN` The image keeps its aspect ratio, but is resized to the maximum size that fits within the Widget's area.
|
||||||
- :cpp:enumerator:`LV_IMAGE_ALIGN_COVER` The image keeps its aspect ratio and fills the Widget's area.
|
- :cpp:enumerator:`LV_IMAGE_ALIGN_COVER` The image keeps its aspect ratio and fills the Widget's area.
|
||||||
|
|
||||||
|
Data binding
|
||||||
|
------------
|
||||||
|
|
||||||
|
To get familiar with observers, subjects, and data bindings in general visit the
|
||||||
|
:ref:`Observer <observer_how_to_use>` page.
|
||||||
|
|
||||||
|
This method of subscribing to a pointer Subject affects an Image Widget's source (``src``)
|
||||||
|
value directly. Note that this is a one-way binding (Subject ==> Widget) so when
|
||||||
|
the subject changes the Image will be update too.
|
||||||
|
|
||||||
|
It support only pointer subjects.
|
||||||
|
|
||||||
|
- :cpp:expr:`lv_image_bind_src(slider, &subject)`
|
||||||
|
|
||||||
.. _lv_image_events:
|
.. _lv_image_events:
|
||||||
|
|
||||||
|
|||||||
@@ -155,6 +155,7 @@ Note that this has a visible effect only if:
|
|||||||
- the Label Widget's width is larger than the width of the longest line of text, and
|
- the Label Widget's width is larger than the width of the longest line of text, and
|
||||||
- the text has multiple lines with different line lengths.
|
- the text has multiple lines with different line lengths.
|
||||||
|
|
||||||
|
|
||||||
.. _lv_label_very_long_texts:
|
.. _lv_label_very_long_texts:
|
||||||
|
|
||||||
Very long text
|
Very long text
|
||||||
@@ -185,6 +186,69 @@ the :ref:`font` section to learn more about symbols.
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.. _lv_label_data_binding:
|
||||||
|
|
||||||
|
Data binding
|
||||||
|
------------
|
||||||
|
|
||||||
|
.. |deg| unicode:: U+000B0 .. DEGREE SIGN
|
||||||
|
|
||||||
|
To get familiar with observers, subjects, and data bindings in general visit the
|
||||||
|
:ref:`Observer <observer_how_to_use>` page.
|
||||||
|
|
||||||
|
This method of subscribing to a Subject affects a Label Widget's
|
||||||
|
``text``. The Subject can be an STRING, POINTER or INTEGER type.
|
||||||
|
|
||||||
|
When the subscription occurs, and each time the Subject's value is changed thereafter,
|
||||||
|
the Subject's value is used to update the Label's text as follows:
|
||||||
|
|
||||||
|
:string Subject: Subject's string is used to directly update the Label's text.
|
||||||
|
|
||||||
|
:pointer Subject: If NULL is passed as the ``format_string`` argument when
|
||||||
|
subscribing, the Subject's pointer value is assumed to point to a
|
||||||
|
NUL-terminated string and is used to directly update the Label's
|
||||||
|
text. See :ref:`observer_format_string` for other options.
|
||||||
|
|
||||||
|
:integer Subject: Subject's integer value is used with the ``format_string`` argument.
|
||||||
|
See :ref:`observer_format_string` for details.
|
||||||
|
|
||||||
|
Note that this is a one-way binding (Subject ===> Widget).
|
||||||
|
|
||||||
|
- :cpp:expr:`lv_label_bind_text(label, &subject, format_string)`
|
||||||
|
|
||||||
|
.. _observer_format_string:
|
||||||
|
|
||||||
|
The ``format_string`` Argument
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
The ``format_string`` argument is optional and if provided, must contain exactly 1
|
||||||
|
printf-like format specifier and be one of the following:
|
||||||
|
|
||||||
|
:string or pointer Subject: "%s" to format the new pointer value as a string or "%p"
|
||||||
|
to format the pointer as a pointer (typically the
|
||||||
|
pointer's address value is spelled out with 4, 8 or 16
|
||||||
|
hexadecimal characters depending on the platform).
|
||||||
|
|
||||||
|
:integer Subject: "%d" format specifier (``"%" PRIdxx`` --- a
|
||||||
|
cross-platform equivalent where ``xx`` can be ``8``,
|
||||||
|
``16``, ``32`` or ``64``, depending on the platform).
|
||||||
|
|
||||||
|
:float Subject: "%f" format specifier, or e.g. "%0.2f" to display two digits after the decimal point.
|
||||||
|
|
||||||
|
|
||||||
|
If ``NULL`` is passed for the ``format_string`` argument:
|
||||||
|
|
||||||
|
:string or pointer Subject: Updates expect the pointer to point to a NUL-terminated string.
|
||||||
|
:integer Subject: The Label will simply display the number. Equivalent to "%d".
|
||||||
|
:float Subject: The Label will display the value with "%0.1f" format string.
|
||||||
|
|
||||||
|
**Example:** "%d |deg|\C"
|
||||||
|
|
||||||
|
As usual with format strings, ``%%`` shall be used to get ``%``. For example ``%d%%``
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.. _lv_label_events:
|
.. _lv_label_events:
|
||||||
|
|
||||||
Events
|
Events
|
||||||
|
|||||||
@@ -76,6 +76,21 @@ This function calculates the height with the current style. If the font,
|
|||||||
line space, border width, etc. of the Roller changes, this function needs
|
line space, border width, etc. of the Roller changes, this function needs
|
||||||
to be called again.
|
to be called again.
|
||||||
|
|
||||||
|
Data binding
|
||||||
|
------------
|
||||||
|
|
||||||
|
To get familiar with observers, subjects, and data bindings in general visit the
|
||||||
|
:ref:`Observer <observer_how_to_use>` page.
|
||||||
|
|
||||||
|
This method of subscribing to an integer Subject affects a Roller Widget's integer
|
||||||
|
value directly. Note that this is a two-way binding (Subject <===> Widget) so an end
|
||||||
|
user's direct interaction with the Roller Widget updates the Subject's value and vice
|
||||||
|
versa.
|
||||||
|
|
||||||
|
It supports only integer subjects.
|
||||||
|
|
||||||
|
- :cpp:expr:`lv_roller_bind_value(roller, &subject)`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.. _lv_roller_events:
|
.. _lv_roller_events:
|
||||||
|
|||||||
@@ -184,6 +184,25 @@ range. If a Section's range is not within the Scale's range at all, it will not
|
|||||||
used in drawing. That can be useful to temporarily "disable" a Section, e.g.
|
used in drawing. That can be useful to temporarily "disable" a Section, e.g.
|
||||||
:cpp:expr:`lv_scale_section_set_range(section, 0, -1)`.)
|
:cpp:expr:`lv_scale_section_set_range(section, 0, -1)`.)
|
||||||
|
|
||||||
|
Data binding
|
||||||
|
~~~~~~~~~~~~
|
||||||
|
|
||||||
|
To get familiar with observers, subjects, and data bindings in general, visit the
|
||||||
|
:ref:`Observer <observer_how_to_use>` page.
|
||||||
|
|
||||||
|
This method of subscribing to an integer Subject affects a Section of a Scale Widget's integer
|
||||||
|
minimum or maximum values directly. Note that this is a one-way binding (Subject ==> Widget)
|
||||||
|
as the Scale Section's boundaries are not interactive.
|
||||||
|
|
||||||
|
|
||||||
|
It supports only integer subjects.
|
||||||
|
|
||||||
|
- :cpp:expr:`lv_scale_bind_section_min_value(scale, section1, &subject)`
|
||||||
|
- :cpp:expr:`lv_scale_bind_section_max_value(scale, section1, &subject)`
|
||||||
|
|
||||||
|
|
||||||
|
.. _change_subject_on_event:
|
||||||
|
|
||||||
|
|
||||||
.. _scale_styling_sections:
|
.. _scale_styling_sections:
|
||||||
|
|
||||||
|
|||||||
@@ -93,6 +93,20 @@ feature is enabled by adding the :cpp:enumerator:`LV_OBJ_FLAG_ADV_HITTEST` flag:
|
|||||||
Any extended click area (set by :cpp:expr:`lv_obj_set_ext_click_area(slider, value)`)
|
Any extended click area (set by :cpp:expr:`lv_obj_set_ext_click_area(slider, value)`)
|
||||||
increases the knob's click area.
|
increases the knob's click area.
|
||||||
|
|
||||||
|
Data binding
|
||||||
|
------------
|
||||||
|
|
||||||
|
To get familiar with observers, subjects, and data bindings in general, visit the
|
||||||
|
:ref:`Observer <observer_how_to_use>` page.
|
||||||
|
|
||||||
|
This method of subscribing to an integer Subject affects a Slider Widget's integer
|
||||||
|
value directly. Note that this is a two-way binding (Subject <===> Widget), so an end
|
||||||
|
user's direct interaction with the Slider Widget updates the Subject's value and vice
|
||||||
|
versa.
|
||||||
|
|
||||||
|
It supports integer and float subjects.
|
||||||
|
|
||||||
|
- :cpp:expr:`lv_slider_bind_value(slider, &subject)`
|
||||||
|
|
||||||
|
|
||||||
.. _lv_slider_events:
|
.. _lv_slider_events:
|
||||||
|
|||||||
@@ -146,6 +146,20 @@ of lines to be displayed in :cpp:enumerator:`LV_SPAN_MODE_BREAK` mode. A negativ
|
|||||||
value indicates no limit.
|
value indicates no limit.
|
||||||
|
|
||||||
|
|
||||||
|
Data binding
|
||||||
|
------------
|
||||||
|
|
||||||
|
To get familiar with observers, subjects, and data bindings in general, visit the
|
||||||
|
:ref:`Observer <observer_how_to_use>` page.
|
||||||
|
|
||||||
|
Very similar to Label-text binding, a Span's text can be bound to a subject as well.
|
||||||
|
|
||||||
|
The only difference is that in the bind function both the Spangroup and the Span need to be specified:
|
||||||
|
|
||||||
|
:cpp:expr:`lv_spangroup_bind_span_text(spangroup, span1, &subject, format_string)`
|
||||||
|
|
||||||
|
Note that before calling :cpp:expr:`lv_spangroup_delete_span`, :cpp:expr:`lv_observer_remove`
|
||||||
|
needs to be called manually as LVGL can't remove the binding automatically.
|
||||||
|
|
||||||
.. _lv_spangroup_events:
|
.. _lv_spangroup_events:
|
||||||
|
|
||||||
|
|||||||
@@ -67,10 +67,19 @@ void lv_xml_bar_apply(lv_xml_parser_state_t * state, const char ** attrs)
|
|||||||
bool anim = anim_str ? lv_xml_to_bool(anim_str) : false;
|
bool anim = anim_str ? lv_xml_to_bool(anim_str) : false;
|
||||||
lv_bar_set_start_value(item, v, anim);
|
lv_bar_set_start_value(item, v, anim);
|
||||||
}
|
}
|
||||||
if(lv_streq("min_value", name)) lv_bar_set_min_value(item, lv_xml_atoi(value));
|
else if(lv_streq("min_value", name)) lv_bar_set_min_value(item, lv_xml_atoi(value));
|
||||||
if(lv_streq("max_value", name)) lv_bar_set_max_value(item, lv_xml_atoi(value));
|
else if(lv_streq("max_value", name)) lv_bar_set_max_value(item, lv_xml_atoi(value));
|
||||||
if(lv_streq("orientation", name)) lv_bar_set_orientation(item, orientation_text_to_enum_value(value));
|
else if(lv_streq("orientation", name)) lv_bar_set_orientation(item, orientation_text_to_enum_value(value));
|
||||||
if(lv_streq("mode", name)) lv_bar_set_mode(item, mode_text_to_enum_value(value));
|
else if(lv_streq("mode", name)) lv_bar_set_mode(item, mode_text_to_enum_value(value));
|
||||||
|
else if(lv_streq("bind_value", name)) {
|
||||||
|
lv_subject_t * subject = lv_xml_get_subject(&state->scope, value);
|
||||||
|
if(subject) {
|
||||||
|
lv_bar_bind_value(item, subject);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
LV_LOG_WARN("Subject \"%s\" doesn't exist in bar bind_value", value);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user