module Opal::UI::DSL

Overview

Comprehensive component factory mixin. Can be included in Builder, StackBuilder, Screen, or any custom class.

Direct including types

Defined in:

opal/game.cr
opal/gamepad.cr
opal/html.cr
opal/image/protocols.cr
opal/mermaid.cr
opal/ui/dsl_methods.cr

Instance Method Summary

Instance Method Detail

def <<(el : Element) : Element #

Operator syntax for #add.


[View source]
def add(el : Element) : Element #

Appends an existing element into the current builder context.


[View source]
def add_element(el : Element) : Element #

Appends an element into the current builder context. Builders override this to store in @root or @elements.


[View source]
def ascii_image(image : Opal::Image::PixelBuffer, mode : Symbol | AsciiRenderMode = :half_block, interpolation : Symbol | Opal::Image::Interpolation = :bilinear, ramp : String = AsciiImage::RAMP_STANDARD, colorize : Bool = true, bold : Bool = false, bg : Color | Symbol | String = Color.none) : AsciiImage #

[View source]
def async_markdown_viewer(label : String = "Loading markdown...", scrollable : Bool = true, auto_scroll : Bool = false, scroll_speed : Float64 = 1.0, width : Int32 = 80, &block : -> String) : AsyncMarkdownViewer #

Asynchronous Markdown Viewer with loading throbber


[View source]
def async_mermaid_viewer(label : String = "Generating diagram...", &block : -> String) : AsyncMermaidViewer #

Declarative DSL helper for async mermaid viewer with background computation


[View source]
def badge(label : String, bg : Color | Symbol | String = :blue, fg : Color | Symbol | String = :white, bold : Bool = true) : Badge #

[View source]
def barchart(title : String | Nil = nil, bar_char : Char = '█', max_value : Float64 | Nil = nil, &) : BarChart #

[View source]
def box(border : Symbol | Border = :rounded, border_fg : Color | Symbol | String = Color.none, padding : Int32 = 0, title : String | Nil = nil, title_fg : Color | Symbol | String = :cyan, bg : Color | Symbol | String = Color.none, child : Element | Nil = nil, &) : Box #

[View source]
def box(border : Symbol | Border = :rounded, border_fg : Color | Symbol | String = Color.none, padding : Int32 = 0, title : String | Nil = nil, title_fg : Color | Symbol | String = :cyan, bg : Color | Symbol | String = Color.none, child : Element | Nil = nil) : Box #

[View source]
def button(label : String, icon : String | Nil = nil, variant : Symbol = :primary, style : Symbol | Nil = nil, toggle : Bool = false, active : Bool = false, disabled : Bool = false, shortcut_char : Char | Nil = nil, border : Symbol | Border | Nil = nil, &on_click : Button -> Nil) : Button #

[View source]
def button(label : String, icon : String | Nil = nil, variant : Symbol = :primary, style : Symbol | Nil = nil, toggle : Bool = false, active : Bool = false, disabled : Bool = false, shortcut_char : Char | Nil = nil, border : Symbol | Border | Nil = nil) : Button #

[View source]
def canvas_2d(width : Int32 = 40, height : Int32 = 20, &) : Canvas2D #

[View source]
def character_grid(interval_x : Int32 = 8, interval_y : Int32 = 4, offset_x : Int32 = 0, offset_y : Int32 = 0, style : Symbol | Graphics::GridStylePreset | Graphics::GridGlyphs = :solid, fg : Color = Color.hex("#6272A4"), bg : Color = Color.none, intersection_fg : Color | Nil = Color.hex("#8BE9FD"), major_interval_x : Int32 | Nil = nil, major_interval_y : Int32 | Nil = nil, major_fg : Color | Nil = Color.hex("#BD93F9"), show_horizontal : Bool = true, show_vertical : Bool = true, show_coordinates : Bool = false, show_border : Bool = true, border_color : Color = Color.hex("#6272A4"), border_style : Symbol = :rounded, title : String | Nil = "Character Grid", width : Int32 | Nil = 30, height : Int32 | Nil = 12) : CharacterGrid #

[View source]
def checkbox(label : String, checked : Bool = false, disabled : Bool = false, &on_change : Bool -> Nil) : Checkbox #

[View source]
def checkbox(label : String, checked : Bool = false, disabled : Bool = false) : Checkbox #

[View source]
def code_view(code : String = "", language : Symbol | String = :plain, start_line : Int32 = 1, highlighted_line : Int32 | Nil = nil, scroll_offset : Int32 = 0, show_line_numbers : Bool = true, gutter_fg : Color | Symbol | String | Nil = nil, cursor_fg : Color | Symbol | String | Nil = nil, cursor_indicator : String | Nil = nil) : CodeView #

[View source]
def collapsible(title : String, collapsed : Bool = true, disabled : Bool = false, child : Element | Nil = nil, &) : Collapsible #

[View source]
def collapsible(title : String, child : Element | Nil = nil, collapsed : Bool = true, disabled : Bool = false) : Collapsible #

[View source]
def color_picker(initial_color : Color = Color.hex("#89B4FA"), active_channel : Symbol = :red, presets : Array(Color) | Nil = nil, mode : ColorMode = ColorMode::RGB, layout : ColorPickerLayout = ColorPickerLayout::Studio, show_alpha : Bool = false, show_harmonies : Bool = true, show_select_button : Bool = false) : ColorPicker #

[View source]
def color_picker_3d(shape : ColorPickerShape = ColorPickerShape::Cube3D, pitch : Float64 = 0.42, yaw : Float64 = 0.58, auto_rotate : Bool = false, initial_color : Color = Color.hex("#89B4FA"), size : Int32 = 12) : ColorPicker3D #

[View source]
def compact_meter(value : Float64) : Text #

Compact 1-character meter glyph


[View source]
def container(title : String | Nil = nil, direction : LayoutDirection = LayoutDirection::Vertical, overflow : OverflowPolicy = OverflowPolicy::Hidden, border : Border = Border.none, padding : Int32 = 0, &) : Group #

Alias for group


[View source]
def content_switcher(current : String | Nil = nil, &) : ContentSwitcher #

[View source]
def curve_editor(p1_x : Float64 = 0.42, p1_y : Float64 = 0.0, p2_x : Float64 = 0.58, p2_y : Float64 = 1.0, width : Int32 = 36, height : Int32 = 16) : CurveEditor #

[View source]
def custom(el : Element) : Element #

Alias for inserting custom elements or controls.


[View source]
def digits(text : String, fg : Color | Symbol | String = :bright_cyan, bg : Color | Symbol | String = Color.none, bold : Bool = true) : Digits #

[View source]
def discussion_link(repo : String, number : Int32 | String, label : String | Nil = nil, fg : Color | Symbol | String = :cyan, bold : Bool = false, underline : Bool = true) : Link #

[View source]
def dock(&) : DockContainer #

[View source]
def dropdown(items : Array(String), selected_index : Int32 = 0, placeholder : String = "Select...", expanded : Bool = false, max_visible_items : Int32 = 6, border : Symbol | Border | Nil = nil, &on_change : Int32, String -> Nil) : Dropdown #

[View source]
def dropdown(items : Array(String), selected_index : Int32 = 0, placeholder : String = "Select...", expanded : Bool = false, max_visible_items : Int32 = 6, border : Symbol | Border | Nil = nil) : Dropdown #

[View source]
def equation_viewer(function_title : String = "f(x) = sin(x)", x_min : Float64 = -5.0, x_max : Float64 = 5.0, y_min : Float64 = -3.0, y_max : Float64 = 3.0, width : Int32 = 42, height : Int32 = 16, &block : Float64 -> Float64) : EquationViewer #

[View source]
def equation_viewer(function_title : String = "f(x) = sin(x)", x_min : Float64 = -5.0, x_max : Float64 = 5.0, y_min : Float64 = -3.0, y_max : Float64 = 3.0, width : Int32 = 42, height : Int32 = 16) : EquationViewer #

[View source]
def file_dialog(initial_path : String = ".", mode : Symbol = :open_file, show_hidden : Bool = false, preview_fn : Proc(String, String) | Nil = nil) : FileDialog #

[View source]
def footer(bindings : Array(NamedTuple(key: String, desc: String)) = [] of NamedTuple(key: String, desc: String)) : Footer #

[View source]
def game_loop(target_fps : Int32 = 60, tick_rate : Int32 = 60, &block : Game::Loop -> Nil) : Game::Loop #

DSL extension for Game Loop


[View source]
def gamepad_controller(virtual_cursor : Bool = false, deadzone : Float64 = 0.15, &block : Input::Gamepad::Event -> Nil) : Input::Gamepad::Driver #

DSL helper to instantiate and listen to a gamepad controller


[View source]
def gauge(ratio : Float64, label : String | Nil = nil, color : Color | Symbol | String | Nil = nil, filled_char : Char = '█', empty_char : Char = '░') : Gauge #

[View source]
def github_link(repo : String, kind : Symbol | String, number : Int32 | String, label : String | Nil = nil, id : String | Nil = nil, fg : Color | Symbol | String = :cyan, bg : Color | Symbol | String = Color.none, bold : Bool = false, dim : Bool = false, italic : Bool = false, underline : Bool = true, icon : String | Nil = nil) : Link #

Declarative GitHub OSC 8 hyperlink element (:rfc, :discussion, :issue, :pr)


[View source]
def grid(columns : Array(GridTrack | Int32 | Float64 | String) = [GridTrack.fr(1.0)], rows : Array(GridTrack | Int32 | Float64 | String) = [GridTrack.fr(1.0)], gutter_x : Int32 = 1, gutter_y : Int32 = 0, &) : GridContainer #

[View source]
def group(title : String | Nil = nil, direction : LayoutDirection = LayoutDirection::Vertical, overflow : OverflowPolicy = OverflowPolicy::Hidden, border : Border = Border.none, padding : Int32 = 0, &) : Group #

Group layout container supporting directional flow and overflow clipping


[View source]
def header(title : String = "Opal Application", subtitle : String | Nil = nil, icon : String | Nil = "[*]", show_clock : Bool = true) : Header #

[View source]
def hex_viewer(bytes : Bytes | Slice(UInt8) | Array(UInt8), base_address : UInt64 = 0_u64, bytes_per_row : Int32 = 16, scroll_offset : Int32 = 0, selected_byte : Int32 | Nil = nil, address_fg : Color | Symbol | String = :dark_gray, zero_fg : Color | Symbol | String = :dark_gray, non_zero_fg : Color | Symbol | String = Color.none, ascii_fg : Color | Symbol | String = :cyan, selected_fg : Color | Symbol | String = :black, selected_bg : Color | Symbol | String = :yellow) : HexViewer #

[View source]
def hsplit(ratio : Float64 | Nil = 0.5, first_width : Int32 | Nil = nil, second_width : Int32 | Nil = nil, min_first_width : Int32 | Nil = nil, max_first_width : Int32 | Nil = nil, separator : Char | Nil = nil, separator_fg : Color | Symbol | String = Color.none, focused_pane : Symbol = :first, show_separator : Bool = true, &) : SplitView #

[View source]
def hstack(spacing : Int32 = 0, &) : HStack #

[View source]
def hstack(elements : Enumerable(Element) = [] of Element, spacing : Int32 = 0) : HStack #

[View source]
def html_browser(initial_url : String = "about:home", enable_osc8 : Bool = true, &block : HTMLBrowser -> Nil) : HTMLBrowser #

Declarative DSL helper to construct a TUI HTML web browser component


[View source]
def html_browser(initial_url : String = "about:home", enable_osc8 : Bool = true) : HTMLBrowser #

[View source]
def image_view(path : String, protocol : Image::Protocol = Image::Protocol::Auto) : ImageView #

[View source]
def input_remapper(model : Input::Core::InputRemapModel, title : String = "Input Remapping", show_border : Bool = true, show_categories : Bool = true, show_deadzone : Bool = true, border : Symbol | Border = :rounded, width : Int32 | Nil = 68, height : Int32 | Nil = 18) : InputRemapper #

Interactive Input Remapper component configured with an existing InputRemapModel


[View source]
def input_remapper(title : String = "Input Remapping", conflict_policy : Input::Core::ConflictPolicy | Symbol | String = :allow, gamepad_style : Input::Core::GamepadStyle | Symbol | String = :dualshock, max_slots : Int32 = 2, show_border : Bool = true, show_categories : Bool = true, show_deadzone : Bool = true, border : Symbol | Border = :rounded, width : Int32 | Nil = 68, height : Int32 | Nil = 18, &) : InputRemapper #

Interactive Input Remapper component configured via declarative DSL block


[View source]
def input_remapper(title : String = "Input Remapping", conflict_policy : Input::Core::ConflictPolicy | Symbol | String = :allow, gamepad_style : Input::Core::GamepadStyle | Symbol | String = :dualshock, max_slots : Int32 = 2, show_border : Bool = true, show_categories : Bool = true, show_deadzone : Bool = true, border : Symbol | Border = :rounded, width : Int32 | Nil = 68, height : Int32 | Nil = 18) : InputRemapper #

Interactive Input Remapper component with default profile


[View source]
def issue_link(repo : String, number : Int32 | String, label : String | Nil = nil, fg : Color | Symbol | String = :cyan, bold : Bool = false, underline : Bool = true) : Link #

[View source]
def line_graph(title : String | Nil = nil, min_y : Float64 | Nil = nil, max_y : Float64 | Nil = nil, show_grid : Bool = true, show_legend : Bool = true, &) : LineGraph #

[View source]
def link(url : String, label : String | Nil = nil, id : String | Nil = nil, fg : Color | Symbol | String = :cyan, bg : Color | Symbol | String = Color.none, bold : Bool = false, dim : Bool = false, italic : Bool = false, underline : Bool = true, icon : String | Nil = nil) : Link #

Declarative OSC 8 terminal hyperlink element link(url, label)


[View source]
def loading_indicator(label : String | Nil = nil, style : Symbol = :dots, fg : Color | Symbol | String = Color.none) : LoadingIndicator #

[View source]
def markdown(content : String, width : Int32 = 80) : MarkdownElement #

[View source]
def markdown_viewer(content : String, scrollable : Bool = true, auto_scroll : Bool = false, scroll_speed : Float64 = 1.0, width : Int32 = 80) : MarkdownViewer #

Interactive Markdown Viewer element


[View source]
def mask(mask_map : MaskMap, feather : Bool = true, offset_x : Int32 = 0, offset_y : Int32 = 0, &) : MaskContainer #

Masking container rendering children under an active MaskMap


[View source]
def mermaid_viewer(diagram_text : String, scrollable : Bool = true, auto_scroll : Bool = false, scroll_speed : Float64 = 1.0, border : Border = Border.rounded) : MermaidViewer #

Declarative DSL helper to construct a Mermaid diagram viewer


[View source]
def mesh_3d(mesh : Opal::Graphics::Mesh3D, camera_dist : Float64 = 3.5, pitch : Float64 = 0.0, yaw : Float64 = 0.0, roll : Float64 = 0.0, render_mode : Symbol | MeshRenderMode = :wireframe, border : Symbol | Border = :rounded, width : Int32 | Nil = nil, height : Int32 | Nil = nil) : Mesh3DElement #

[View source]
def meter(value : Float64, orientation : MeterOrientation = MeterOrientation::Horizontal, gradient : MeterGradient = MeterGradient::Heat, show_label : Bool = true, width : Int32 | Nil = nil, height : Int32 | Nil = nil) : Meter #

Fractional 1/8th Unicode block meter


[View source]
def mock_gamepad(driver : Input::Gamepad::Driver = Input::Gamepad::Driver.new, &block : Input::Gamepad::MockDSL -> Nil) : Input::Gamepad::Driver #

DSL helper to simulate mock gamepad inside UI trees


[View source]
def modal(title : String, message : String, buttons : Array(String) = ["OK"], selected_button : Int32 = 0, border_fg : Color | Symbol | String = :cyan, title_fg : Color | Symbol | String = :bright_white, button_fg : Color | Symbol | String = :white, selected_fg : Color | Symbol | String = :black, selected_bg : Color | Symbol | String = :cyan, dim_backdrop : Bool = true) : Modal #

[View source]
def palette(model : PaletteModel, picker_mode : ColorMode = ColorMode::RGB, picker_layout : ColorPickerLayout = ColorPickerLayout::Sliders, title : String = "Color Palette", width : Int32 | Nil = 54, height : Int32 | Nil = 16) : Palette #

[View source]
def palette(colors : Array(Color), min_colors : Int32 | Nil = nil, max_colors : Int32 | Nil = nil, allow_add : Bool = true, allow_remove : Bool = true, allow_reorder : Bool = true, picker_mode : ColorMode = ColorMode::RGB, picker_layout : ColorPickerLayout = ColorPickerLayout::Sliders, title : String = "Indexed Palette", width : Int32 | Nil = 54, height : Int32 | Nil = 16) : Palette #

[View source]
def palette(entries : Hash(String, Color), allow_add : Bool = true, allow_rename : Bool = true, allow_remove : Bool = true, picker_mode : ColorMode = ColorMode::RGB, picker_layout : ColorPickerLayout = ColorPickerLayout::Sliders, title : String = "Named Palette", width : Int32 | Nil = 54, height : Int32 | Nil = 16) : Palette #

[View source]
def pie_chart(title : String | Nil = nil, donut : Bool = false, &) : PieChart #

[View source]
def placeholder(label : String | Nil = nil, border : Symbol | Border = :rounded) : Placeholder #

[View source]
def pr_link(repo : String, number : Int32 | String, label : String | Nil = nil, fg : Color | Symbol | String = :cyan, bold : Bool = false, underline : Bool = true) : Link #

[View source]
def radio_set(options : Array(String) | Nil = nil, items : Array(String) | Nil = nil, selected_index : Int32 = 0, disabled : Bool = false, horizontal : Bool = false, &on_change : Int32, String -> Nil) : RadioSet #

[View source]
def radio_set(options : Array(String) | Nil = nil, items : Array(String) | Nil = nil, selected_index : Int32 = 0, disabled : Bool = false, horizontal : Bool = false) : RadioSet #

[View source]
def rfc_link(repo : String, number : Int32 | String, label : String | Nil = nil, fg : Color | Symbol | String = :cyan, bold : Bool = false, underline : Bool = true) : Link #

[View source]
def rich_log(max_lines : Int32 = 1000, auto_scroll : Bool = true, highlight_ansi : Bool = true) : RichLog #

[View source]
def rule(char : Char = '─', fg : Color | Symbol | String = Color.none) : Rule #

[View source]
def scissor(x : Int32, y : Int32, width : Int32, height : Int32, &) : ScissorContainer #

[View source]
def scissor(rect : Rect, &) : ScissorContainer #

Scissor clipping container restricting child rendering to a specific rect


[View source]
def scrollbar(orientation : ScrollBar::Orientation | Symbol = ScrollBar::Orientation::Vertical, min_value : Int32 = 0, max_value : Int32 = 100, value : Int32 = 0, page_size : Int32 = 10, length : Int32 | Nil = nil, total : Int32 | Nil = nil, show_arrows : Bool = true, &on_scroll : Int32 -> Nil) : ScrollBar #

[View source]
def scrollbar(orientation : ScrollBar::Orientation | Symbol = ScrollBar::Orientation::Vertical, min_value : Int32 = 0, max_value : Int32 = 100, value : Int32 = 0, page_size : Int32 = 10, length : Int32 | Nil = nil, total : Int32 | Nil = nil, show_arrows : Bool = true) : ScrollBar #

[View source]
def slider(min : Number = 0.0, max : Number = 100.0, value : Number = 0.0, step : Number = 1.0, label : String | Nil = nil, show_value : Bool = true, disabled : Bool = false, color : Color | Symbol | String | Nil = nil, &on_change : Float64 -> Nil) : Slider #

[View source]
def slider(min : Number = 0.0, max : Number = 100.0, value : Number = 0.0, step : Number = 1.0, label : String | Nil = nil, show_value : Bool = true, disabled : Bool = false, color : Color | Symbol | String | Nil = nil) : Slider #

[View source]
def sparkline(data : Array(Float64), color : Color | Symbol | String = :cyan, title : String | Nil = nil, min : Float64 | Nil = nil, max : Float64 | Nil = nil) : Sparkline #

[View source]
def split_pane(direction : SplitDirection | Symbol | String = SplitDirection::Horizontal, ratio : Float64 | Nil = 0.5, first_size : Int32 | Nil = nil, second_size : Int32 | Nil = nil, min_first_size : Int32 | Nil = nil, max_first_size : Int32 | Nil = nil, separator : Char | Nil = nil, separator_fg : Color | Symbol | String = Color.none, focused_pane : Symbol = :first, show_separator : Bool = true, &) : SplitView #

[View source]
def split_view(direction : SplitDirection | Symbol | String = SplitDirection::Horizontal, ratio : Float64 | Nil = 0.5, first_size : Int32 | Nil = nil, second_size : Int32 | Nil = nil, min_first_size : Int32 | Nil = nil, max_first_size : Int32 | Nil = nil, separator : Char | Nil = nil, separator_fg : Color | Symbol | String = Color.none, focused_pane : Symbol = :first, show_separator : Bool = true, &) : SplitView #

[View source]
def switch(label : String, on : Bool = false, disabled : Bool = false, &on_change : Bool -> Nil) : Switch #

[View source]
def switch(label : String, on : Bool = false, disabled : Bool = false) : Switch #

[View source]
def table(headers : Array(String) = [] of String, header_fg : Color | Symbol | String = :cyan, border_fg : Color | Symbol | String = Color.none, &) : Table #

[View source]
def table(headers : Array(String) = [] of String, rows : Array(Array(String)) = [] of Array(String), header_fg : Color | Symbol | String = :cyan, border_fg : Color | Symbol | String = Color.none) : Table #

[View source]
def tabs(labels : Array(String), active_index : Int32 = 0, active_fg : Color | Symbol | String = :bright_white, active_bg : Color | Symbol | String = :blue, inactive_fg : Color | Symbol | String = :gray, pill_style : Bool = false) : Tabs #

[View source]
def tabs(items : Array(TabItem), active_index : Int32 = 0, active_fg : Color | Symbol | String = :bright_white, active_bg : Color | Symbol | String = :blue, inactive_fg : Color | Symbol | String = :gray, spacing : Int32 = 2, pill_style : Bool = false) : Tabs #

[View source]
def target_selector_2d(x_range : Range(Float64, Float64) = 0.0..1.0, y_range : Range(Float64, Float64) = 0.0..1.0, initial_x : Float64 | Nil = nil, initial_y : Float64 | Nil = nil, reticle_char : Char = '⌖', show_coordinates : Bool = true, border : Bool = true, width : Int32 | Nil = 24, height : Int32 | Nil = 10, &block : Float64, Float64 -> Color) : TargetSelector2D #

[View source]
def target_selector_2d(x_range : Range(Float64, Float64) = 0.0..1.0, y_range : Range(Float64, Float64) = 0.0..1.0, initial_x : Float64 | Nil = nil, initial_y : Float64 | Nil = nil, reticle_char : Char = '⌖', show_coordinates : Bool = true, border : Bool = true, width : Int32 | Nil = 24, height : Int32 | Nil = 10) : TargetSelector2D #

[View source]
def text(content : String, fg : Color | Symbol | String = Color.none, bg : Color | Symbol | String = Color.none, bold : Bool = false, dim : Bool = false, italic : Bool = false, underline : Bool = false) : Text #

[View source]
def tree(title : String | Nil = nil, &) : Tree #

[View source]
def tween(from_val : Float64, to_val : Float64, duration : Time::Span, easing : Animation::Easing = Animation::Easing::Linear, &block : Float64 -> Nil) : Animation::Tween #

Tween animator helper


[View source]
def viewport(content : String, offset_y : Int32 = 0, fg : Color | Symbol | String = Color.none, show_scrollbar : Bool = true) : Viewport #

[View source]
def vsplit(ratio : Float64 | Nil = 0.5, first_height : Int32 | Nil = nil, second_height : Int32 | Nil = nil, min_first_height : Int32 | Nil = nil, max_first_height : Int32 | Nil = nil, separator : Char | Nil = nil, separator_fg : Color | Symbol | String = Color.none, focused_pane : Symbol = :first, show_separator : Bool = true, &) : SplitView #

[View source]
def vstack(spacing : Int32 = 0, &) : VStack #

[View source]
def vstack(elements : Enumerable(Element) = [] of Element, spacing : Int32 = 0) : VStack #

[View source]
def window(title : String, x : Int32 = 0, y : Int32 = 0, width : Int32 = 40, height : Int32 = 15, border : Symbol | Border = :rounded, resizable : Bool = true, minimizable : Bool = true, maximizable : Bool = true, child : Element | Nil = nil, &) : Window #

[View source]
def window(title : String, x : Int32 = 0, y : Int32 = 0, width : Int32 = 40, height : Int32 = 15, border : Symbol | Border = :rounded, resizable : Bool = true, minimizable : Bool = true, maximizable : Bool = true, child : Element | Nil = nil) : Window #

[View source]