Skip to content

Commit

Permalink
Merge branch 'develop'
Browse files Browse the repository at this point in the history
  • Loading branch information
djowel committed Jan 25, 2024
2 parents 5eb220a + b0d4bce commit 5a88c58
Show file tree
Hide file tree
Showing 28 changed files with 1,060 additions and 365 deletions.
1 change: 1 addition & 0 deletions examples/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,4 @@ add_subdirectory(table_list)
add_subdirectory(drop_file)
add_subdirectory(range_slider)
add_subdirectory(model)
add_subdirectory(selection_list)
4 changes: 2 additions & 2 deletions examples/custom_control/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ void my_custom_control::keep_tracking(context const& ctx, tracker_info& track_in
clamp(_radius, 50, 150);
if (on_change)
on_change(_radius);
ctx.view.refresh(ctx.bounds);
ctx.view.refresh(ctx);
}

///////////////////////////////////////////////////////////////////////////////
Expand All @@ -177,7 +177,7 @@ bool my_custom_control::cursor(context const& ctx, point p, cursor_tracking stat
case cursor_tracking::hovering:
case cursor_tracking::entering:
_mouse_over = true;
ctx.view.refresh(ctx.bounds);
ctx.view.refresh(ctx);
break;
case cursor_tracking::leaving:
_mouse_over = false;
Expand Down
2 changes: 1 addition & 1 deletion examples/list/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ auto background = box(bkd_color);

int main(int argc, char* argv[])
{
app _app(argc, argv, "Dynamic Lists", "com.cycfi.dynamic_lists");
app _app(argc, argv, "Lists", "com.cycfi.list");
window _win(_app.name());
_win.on_close = [&_app]() { _app.stop(); };

Expand Down
64 changes: 37 additions & 27 deletions examples/list_arranger/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,32 +16,32 @@ auto make_bkd()

// Probably shouldn't be global, but hey, it's a demo. You know what to do :-)
std::vector<std::filesystem::path> paths = {
"/home/user/documents/quantum_energy_matrix_42.txt",
"/var/www/html/interdimensional_portal_manifest.html",
"/usr/bin/elixir_of_eternal_life.exe",
"/mnt/data/enigmatic_astral_code_vortex.jpg",
"/opt/software/ancient_relics_archeology.ini",
"/tmp/temp/mystic_scroll_of_knowledge.tmp",
"/home/user/documents/hyperdimensional_cosmic_key_99.txt",
"/var/log/transcendental_being_encounter.log",
"/usr/lib/quantum_realm_gateway.so",
"/mnt/data/sacred_harmonic_resonance_music.mp3",
"/opt/software/ethereal_data_oracle.json",
"/tmp/temp/ancient_prophecy_tablet.zip",
"/home/user/documents/esoteric_mind_matrix.txt",
"/var/www/html/arcane_ritual_summoning_page.html",
"/usr/bin/ouroboros_eternal_loop_script.sh",
"/mnt/data/celestial_beauty_revelation_video.mp4",
"/opt/software/sacred_geometry_universe.cfg",
"/tmp/temp/profound_astral_chart.csv",
"/home/user/documents/elixir_of_infinite_wisdom.txt",
"/var/log/mystical_realm_access_error.log",
"/usr/bin/magical_portal_activation.exe",
"/mnt/data/cosmic_energy_matrix_manifest.jpg",
"/opt/software/ancient_tome_of_knowledge.log",
"/tmp/temp/akashic_records_of_creation.txt",
"/home/user/documents/quantum_cosmic_frequencies.txt",
"/var/www/html/ethereal_realm_connection.css",
"a/home/user/documents/quantum_energy_matrix_42.txt",
"b/var/www/html/interdimensional_portal_manifest.html",
"c/usr/bin/elixir_of_eternal_life.exe",
"d/mnt/data/enigmatic_astral_code_vortex.jpg",
"e/opt/software/ancient_relics_archeology.ini",
"f/tmp/temp/mystic_scroll_of_knowledge.tmp",
"g/home/user/documents/hyperdimensional_cosmic_key_99.txt",
"h/var/log/transcendental_being_encounter.log",
"i/usr/lib/quantum_realm_gateway.so",
"j/mnt/data/sacred_harmonic_resonance_music.mp3",
"k/opt/software/ethereal_data_oracle.json",
"l/tmp/temp/ancient_prophecy_tablet.zip",
"m/home/user/documents/esoteric_mind_matrix.txt",
"n/var/www/html/arcane_ritual_summoning_page.html",
"o/usr/bin/ouroboros_eternal_loop_script.sh",
"p/mnt/data/celestial_beauty_revelation_video.mp4",
"q/opt/software/sacred_geometry_universe.cfg",
"r/tmp/temp/profound_astral_chart.csv",
"s/home/user/documents/elixir_of_infinite_wisdom.txt",
"t/var/log/mystical_realm_access_error.log",
"u/usr/bin/magical_portal_activation.exe",
"v/mnt/data/cosmic_energy_matrix_manifest.jpg",
"w/opt/software/ancient_tome_of_knowledge.log",
"x/tmp/temp/akashic_records_of_creation.txt",
"y/home/user/documents/quantum_cosmic_frequencies.txt",
"z/var/www/html/ethereal_realm_connection.css",
"/usr/bin/astral_projection_script.rb",
"/mnt/data/ancient_chants_of_enlightenment_music.mp3",
"/opt/software/celestial_beings_communication.png",
Expand Down Expand Up @@ -83,7 +83,17 @@ int main(int argc, char* argv[])
size_t list_size = paths.size();
auto && make_row = [&](size_t index)
{
return share(draggable(align_left(label(paths[index].u8string()))));
// If we start with an empty paths vector, we still need to give it a prototypical
// element in order to establish the size limits.
std::string path = paths.empty()? std::string{"Empty"} : paths[index].u8string();

return share(
(
draggable(
align_left(label(path))
)
)
);
};

auto cp = basic_vcell_composer(list_size, make_row);
Expand Down
83 changes: 25 additions & 58 deletions examples/model/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,39 +21,23 @@
occur. The application may need the ability to inspect the state of the GUI elements or set
them to a specific state at any given time.
We all know how to implement 1.
A common and straight-forward way to implement 2 and 3 is to expose various GUI elements as
members of an application or GUI class. For example, here's a code snippet of an old plugin I
wrote a few years back:
private:
input_box_ptr _program_id;
label_ptr _position_text;
button_ptr _enable;
toggle_button_ptr _sync;
menu_ptr _preset_menu;
input_box_ptr _save_as_name;
slider_ptr _master_volume;
label_ptr _master_volume_text;
The `input_box_ptr`, `button_ptr`, and so on, are `std::shared_ptr`s that are created using the
`share(e)` function and held in the element hieiarchy using the `hold(p)` function. These are
kept as private members in a GUI class which manages the interconnections and presents a
higher-level view to the application through a well-defined API.
This example presents a more elegant way to structure an elements application using Models.
The Model (elements/model.hpp) serves as an abstraction for a data type that is linked to one
or more user interface elements. The actual data is accessed and modified through the `get` and
A typical approach for steps 2 and 3 involves holding different GUI elements as private members
within an application or GUI class. These elements are managed as shared pointers, created
using the `share(e)` function and held in the element hieiarchy using the `hold(p)` function.
The GUI class oversees the interconnections and presents a higher-level view to the application
through a well-defined API.
This example presents a more elegant way to structure an elements application using Models. The
Model (elements/model.hpp) serves as an abstraction for a data type that is linked to one or
more user interface elements. The actual data is accessed and modified through the `get` and
`set` member functions of the derived class. A user interface element can be linked to a
`model` by supplying an `update_function` via the `on_update_ui(f)` member function.
`model` by supplying an `update_function` via the `on_update(f)` member function.
The Model does not care about the GUI element types it is interacting with. It is an abstract
data type that models its underlying data type (e.g. float, int, enum, etc.). In the view point
of the application, it looks and acts like a concrete type. For example, a `value_model<float>`
acts just like a float. You can assign a value to it:
data type that models its underlying data type (e.g. float, int, enum, etc.). It looks and acts
like a concrete type.
For example, a `value_model<float>` acts just like a float. You can assign a value to it:
m = 1.0;
Expand All @@ -63,7 +47,7 @@
You can also update the GUI elements that are linked to it:
m.update_ui(0.5);
m.update(0.5);
These capabilities implement 2 and 3 of the requirements.
Expand Down Expand Up @@ -92,31 +76,13 @@
5. The GUI and the application are both unaware of each other.
Number 5 is an important design principle known as "decoupling," emphasizing the separation of
concerns and promoting independence between the GUI and the application components.
The advantages of decoupling in a software design context include:
1. Scalability: Decoupling facilitates scalability by enabling the addition or removal of
components without disrupting the entire system. This is particularly important as the
application evolves.
2. Parallel Development: The GUI and application can be developed independently.
3. Reusability: Decoupled components are more reusable in other contexts. They can be extracted
and utilized in different projects without carrying unnecessary dependencies.
4. Maintainability: The overall maintainability of the system is improved because changes to
one component are less likely to cascade through the entire codebase. Maintenance is more
straightforward. Updates or bug fixes to one component can be made without impacting the
rest of the system.
5. Modularity: A notable advantage of this approach is that the user interface elements do not
need to be exposed beyond their creation function. Consequently, all GUI logic is localized
and established within the same element creation function.
concerns and promoting independence between the GUI and the application components. The
advantages of decoupling in a software design context include, modularity, scalability, and
reusability, and ease of maintainance.
In this example, we present a very simple model, comprising of a floating point value and a
preset. As the GUI elements are being built, they attach themselves to the model by utilizing
its on_update_ui(f) member function at different nodes within the elements hierarchy. This
its on_update(f) member function at different nodes within the elements hierarchy. This
illustrates the approach of designing the user interface based on models.
=================================================================================================*/
namespace elements = cycfi::elements;
Expand Down Expand Up @@ -185,7 +151,7 @@ auto make_dial(my_model& model, view& view_)

// When a new value is assigned to the model, we want to update
// the dial and refresh the view.
model._value.on_update_ui(
model._value.on_update(
[&view_, dial_ptr](double val)
{
dial_ptr->value(val);
Expand Down Expand Up @@ -265,11 +231,12 @@ auto make_preset_menu(my_model& model, view& view_)

// When a new preset is assigned to the model, we want to update
// the menu text and refresh the view.
model._preset.on_update_ui(
model._preset.on_update(
[&view_, label = preset_menu.second, &model](my_model::preset val)
{
if (val == my_model::preset_none)
{
// Prepend '*' to the string to indicate that it is being edited.
auto text = label->get_text();
if (text[0] != '*')
label->set_text("*" + std::string{text});
Expand Down Expand Up @@ -300,7 +267,7 @@ auto make_input_box(my_model& model, view& view_)

// When a new value is assigned to the model, we want to update
// the input text box and refresh the view.
model._value.on_update_ui(
model._value.on_update(
[&view_, input = tbox.second](double val)
{
input->set_text(std::to_string(val));
Expand Down Expand Up @@ -351,7 +318,7 @@ auto make_input_box(my_model& model, view& view_)
[&model]()
{
// When errors are enountered, reset the model's value to its previous state.
model._value.update_ui();
model._value.update();
};

// Bring up a message box.
Expand All @@ -360,7 +327,7 @@ auto make_input_box(my_model& model, view& view_)
}
};

return halign(0.5, hsize(70, tbox.first));
return align_center(hsize(70, tbox.first));
}

// Finally, we have our main content.
Expand Down
29 changes: 29 additions & 0 deletions examples/selection_list/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
cmake_minimum_required(VERSION 3.9.6...3.15.0)
project(DynamicLists LANGUAGES C CXX VERSION "1.0.0")

if (NOT ELEMENTS_ROOT)
message(FATAL_ERROR "ELEMENTS_ROOT is not set")
endif()

# Make sure ELEMENTS_ROOT is an absolute path to add to the CMake module path
get_filename_component(ELEMENTS_ROOT "${ELEMENTS_ROOT}" ABSOLUTE)
set (CMAKE_MODULE_PATH "${CMAKE_MODULE_PATH};${ELEMENTS_ROOT}/cmake")

# If we are building outside the project, you need to set ELEMENTS_ROOT:
if (NOT ELEMENTS_BUILD_EXAMPLES)
include(ElementsConfigCommon)
set(ELEMENTS_BUILD_EXAMPLES OFF)
add_subdirectory(${ELEMENTS_ROOT} elements)
endif()

set(ELEMENTS_APP_PROJECT "SelectionList")
set(ELEMENTS_APP_TITLE "Selection List")
set(ELEMENTS_APP_COPYRIGHT "Copyright (c) 2016-2024 Joel de Guzman")
set(ELEMENTS_APP_ID "com.cycfi.selection_list")
set(ELEMENTS_APP_VERSION "1.0")

set(ELEMENTS_APP_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/main.cpp)

# For your custom application icon on macOS or Windows see cmake/AppIcon.cmake module
include(AppIcon)
include(ElementsConfigApp)
95 changes: 95 additions & 0 deletions examples/selection_list/main.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/*=================================================================================================
Copyright (c) 2016-2024 Joel de Guzman
Distributed under the MIT License (https://opensource.org/licenses/MIT)
=================================================================================================*/
#include <elements.hpp>

using namespace cycfi::elements;
using namespace cycfi::artist;

// Main window background color
auto constexpr bkd_color = rgba(35, 35, 37, 255);
auto background = box(bkd_color);

struct my_element : element, selectable
{
my_element(int n)
: _n{n}
{}

void draw(context const& ctx) override
{
auto& cnv = ctx.canvas;
auto state = cnv.new_state();
auto const& theme_ = get_theme();

if (_is_selected)
{
cnv.begin_path();
cnv.add_rect(ctx.bounds);
cnv.fill_style(theme_.indicator_color.opacity(0.6));
cnv.fill();
}

auto middle = ctx.bounds.top + (ctx.bounds.height()/2);
cnv.fill_style(theme_.label_font_color);
cnv.font(theme_.label_font);
cnv.text_align(cnv.left | cnv.middle);
cnv.fill_text(std::string{"Item "} + std::to_string(_n), point{ctx.bounds.left+10, middle});
}

bool is_selected() const override
{
return _is_selected;
}

void select(bool state) override
{
_is_selected = state;
}

int _n;
bool _is_selected = false;
};

int main(int argc, char* argv[])
{
app _app(argc, argv, "Selection Lists", "com.cycfi.selection_lists");
window _win(_app.name());
_win.on_close = [&_app]() { _app.stop(); };

view view_(_win);

auto&& draw_cell =
[](std::size_t index)
{
return share(
margin({20, 0, 20, 0},
align_left(
vsize(25, my_element(index+1))
)
)
);
};

auto my_composer =
basic_cell_composer(
200, // size (number of rows)
draw_cell // Composer function
);

auto content = share(
selection_list(
list{my_composer, false}
)
);

view_.content(
vscroller(hold(content)),
background
);

_app.run();
return 0;
}
Loading

0 comments on commit 5a88c58

Please sign in to comment.