Skip to content

Latest commit

 

History

History
196 lines (154 loc) · 6.33 KB

File metadata and controls

196 lines (154 loc) · 6.33 KB

Pages

  1. Getting started
  2. Layouts
  3. Styling
  4. Components
  5. Writing component

Writing custom component

Preview

img

Description

Suppose we want to write custom component - no matter what it will do, but for this page we will define our future component needs:

  • It should have rectangle as borders
  • We want to accept any component and render it within rect
  • Also we want to fill all free space of it with some character

So, we can simply draw two components separately inside draw loop, but it has two drawbacks:

  1. We cant use it in components, that accepts as params another component, and renders withing itself(like list)
  2. If we need to have more than one of that component, our code will go big too fast.

So, first we start by looking at requirements, defined in object.sh: We need to implement all functions of abstract object, for our component:

# $1 - object name
# Creates new component
object.new

# $1 - object name
# Deletes components
object.delete

# $1 - object name
# $2 - layout
# Draws components within given layout
object.draw

# $1 - object name
# $2 - layout within object will calculate its size
# $3 - variable name for x size
# $4 - variable name for y size
# Calculates minimal size inside given layout 
object.get_minimal_size

# $1 - object name
# $2, $3, ... - names of properties to enable, see SYMBOL_MODIFIERS for more style.set_style
# Sets style for object
object.set_style

lets say our component will be named rect_with_child, so we define it

# here we create associative array, where we will be storing all values for our components. 
# It's not mandatory so you could do it other way
declare -A _rect_with_child_obj
# $1 - object name
# $2 - child component name
# Creates new component
function rect_with_child.new() {
    _rect_with_child_obj["$1"]=1
    _rect_with_child_obj["$1:child"]=$2
    # We say, that object with name, passed as first argument, should be accessed via with child class
    # So any component now can call abstract object functions like object.draw, and it will link them to, for example, rect_with_child.draw
    register_object $1 rect_with_child
    _label_obj["$1:style"]=1
    # We initialize style class with same name as our component, you can use any name
    style.new $1
    # Create rectangle that will act as our border
    rect.new ".rect_with_child_$1"
    rect.get_layout ".rect_with_child_$1" ".rect_with_child_${1}_layout"
}

# $1 - object name
# Deletes components
function rect_with_child.delete() {

    unset _rect_with_child_obj["$1"]
    unset _rect_with_child_obj["$1:child"]
    # Free that name, meaning that passed value $1 will no longer link to rect_with_child
    unregister_object $1
    # Delete style
    style.delete $1
    # Delete rect
    rect.delete ".rect_with_child_$1"
}

# $1 - object name
# $2 - layout
# Draws components within given layout
function rect_with_child.draw() {
    # We get sizes of given to ours layout
    local start_x start_y end_x end_y
    layout.get_rect $2 start_x start_y end_x end_y

    # Every 'pixel' in our layout will show "0" as symbol 
    buffer.insert_rect "0" $start_x $start_y $end_x $end_y
    # Apply style for all pixels in our layout
    style.apply_rect $1 $start_x $start_y $end_x $end_y
    # Draw our child rect
    rect.draw ".rect_with_child_$1" $2

    # We dont know what type a given component is. So we draw it with object abstraction
    object.draw ${_rect_with_child_obj["$1:child"]} ".rect_with_child_${1}_layout"

}

# $1 - object name
# $2 - layout within object will calculate its size
# $3 - variable name for x size
# $4 - variable name for y size
# Calculates minimal size inside given layout 
function rect_with_child.get_minimal_size() {
    # We export minimal size of our components. Lets say it will be 5x5
    export "$3=5" 
    export "$4=5"
}

# $1 - object name
# $2, $3, ... - names of properties to enable, see SYMBOL_MODIFIERS for more style.set_style
# Sets style for object
function rect_with_child.set_style() {
    # Pass style params to style class
    style.set_style $1 "${@:2}"
}

And later we could use it as component

source ../src/lib.sh
# Import our defined component
source ./rect_with_child.sh
init_screen

layout.create_from_screen Layout

label.new label "Hello!"
rect_with_child.new rect1 label


rect_with_child.draw rect1 Layout
buffer.flush
sleep 10
exit_screen

But heres more - remember that we can pass any component to it, because we use object abstraction? We can pass other rect_with_child!

Code of rect_with_child_example.sh below

source ../src/lib.sh
# Import our defined component
source ./rect_with_child.sh
init_screen

layout.create_from_screen Layout

label.new label "Hello!"
rect_with_child.new rect1 label

rect_with_child.new rect2 rect1
rect_with_child.new rect3 rect2



rect_with_child.draw rect3 Layout
buffer.flush
sleep 10
exit_screen

Render more, by calling less

You may have questions about that line buffer.insert_rect "0" $start_x $start_y $end_x $end_y or style.apply_rect $1 $start_x $start_y $end_x $end_y

First lets see buffer

Defined in buffer.sh

It has 4 functions for displaying symbols:

  • buffer.insert_rect_border - draw symbol in border of rect
  • buffer.insert_rect - fill full rect (or line) with given symbol
  • buffer.insert_text - prints text in single line
  • buffer.insert - simply put character at given position

Yes, you could always use buffer.insert within loop, but bash is slow, so its more preferably to call suitable functions for bigger sizes.

Why? Well, actually, when you call those 4 functions, you are not inserting symbol directly - you are giving command like "lets fill that rect with a letter".

And during buffer.flush theese commands are processed within awk and rendered inside of it. At beggining of development this library, i have used bash loop, but it quickly turned out that it takes not a small amount of time to process every pixel in for loop, so i tried switching to awk and it gave me some perfomance boost.

Same goes for style.

Defined in style.sh

Better to call style.apply_rect, than style.apply for individual pixel in a loop