Pages
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:
- We cant use it in components, that accepts as params another component, and renders withing itself(like list)
- 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_stylelets 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_screenBut 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_screenYou 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
