scrollbar.C
creates a scroll-bar below a
checkbox and an
input field.
The scroll-bar supposedly controls a volume value of some sort, which
can be set to a range of 0 through 11.
As the scroll-bar's value gets adjusted, the input field above it
reflects the updated volume value. A new value can be explicitly typed
into the input field, and this manually moves the scrollbar.
The checkbox adds (or removes) a decimal point, making the scrollbar's
value range from 0.0 through 11.0.
/* ** Copyright 2017-2021 Double Precision, Inc. ** See COPYING for distribution information. */ #include "config.h" #include <x/mpobj.H> #include <x/exception.H> #include <x/destroy_callback.H> #include <x/ref.H> #include <x/obj.H> #include <x/weakcapture.H> #include <x/appid.H> #include <x/w/main_window.H> #include <x/w/label.H> #include <x/w/gridlayoutmanager.H> #include <x/w/gridfactory.H> #include <x/w/scrollbar.H> #include <x/w/image_button.H> #include <x/w/input_field.H> #include <x/w/container.H> #include <x/w/key_event.H> #include <x/w/dialog.H> #include "close_flag.H" #include <string> #include <string_view> #include <iostream> #include <iomanip> #include <sstream> #include <cmath> std::string x::appid() noexcept { return "scrollbar.examples.w.libcxx.com"; } // Need an object to keep track of some metadata, that can be captured // by several callbacks. class volume_infoObj : virtual public x::obj { public: bool use_decimals=false; double current_volume=0; // Return a scrollbar that ranges 0-11, or 0-110. auto scrollbar_config() const { x::w::scrollbar_config config{12, 1}; if (use_decimals) { config.range=120; config.page_size=10; config.value=(int)std::round(current_volume * 10); } else { config.value=(int)current_volume; } config.minimum_size=75; // 75 millimeters wide. return config; } void set_volume(double v, const x::w::input_field &input_field) { current_volume=use_decimals ? v/10:v; std::ostringstream o; if (use_decimals) o << std::fixed << std::setprecision(1); o << current_volume; input_field->set(o.str()); } void set_volume(const x::w::input_field &input_field, const x::w::scrollbar &scrollbar, const x::w::main_window &main_window) { std::string current_volume=x::w::input_lock{input_field}.get(); std::istringstream i{current_volume}; double v; if ( (i >> v) && i.get() == std::istream::traits_type::eof()) { if (use_decimals) v *= 10; int n=std::round(v); if (n >= 0 && n <= (use_decimals ? 110:11)) { scrollbar->set(n); return; } } // Bad input. Show an error dialog. auto d=main_window->create_ok_dialog ({"error@scrollbar.examples.w.libcxx.com", true}, "alert", [] (const x::w::factory &f) { f->create_label("Bad input"); }, main_window->destroy_when_closed ("error@scrollbar.examples.w.libcxx.com")); d->dialog_window->set_window_title("Error"); main_window->set_window_class ("main", "scrollbar.examples.w.libcxx.com"); d->dialog_window->show_all(); } void set_decimals(const x::w::input_field &input_field, const x::w::scrollbar &scrollbar) { scrollbar->reconfigure(scrollbar_config()); set_volume(current_volume, input_field); } }; typedef x::ref<volume_infoObj> volume_info; void initialize_volume_control(const x::w::main_window &main_window) { auto layout=main_window->gridlayout(); x::w::gridfactory factory=layout->append_row(); auto vi=volume_info::create(); x::w::image_button checkbox= factory->create_checkbox([] (const auto &f) { f->create_label("Volume has decimal points"); }); factory=layout->append_row(); // Create an input_field, with a '%' immediately afterwards. Center it // because the scrollbar will be below it, and wider. For doing this, // we'll create an inner container, that's centered. factory->halign(x::w::halign::center); x::w::input_fieldptr input_fieldptr; factory->create_container ([&] (const auto &container) { auto glm=container->gridlayout(); glm->row_alignment(0, x::w::valign::middle); auto row=glm->append_row(); // Don't need any padding, the main grid default // padding will suffice. row->padding(0); x::w::input_field_config config{5}; config.alignment=x::w::halign::right; input_fieldptr=row->create_input_field("0", config); row->create_label("%"); }, x::w::new_gridlayoutmanager{}); x::w::input_field input_field=input_fieldptr; factory=layout->append_row(); x::w::scrollbar sb= factory->create_horizontal_scrollbar(vi->scrollbar_config(), [vi, input_field] (ONLY IN_THREAD, const x::w::scrollbar_info_t &status) { vi->set_volume(status.dragged_value, input_field); }); // Add a key event to the input field, for the Enter key, to set the // manually typed-in value as the explicit value. input_field->on_key_event([vi, fields=x::make_weak_capture(sb, input_field, main_window)] (ONLY IN_THREAD, const auto &why, bool activated, const auto &ignore) { // Verify that that a key_event gets // passed in. if (!std::holds_alternative<const x::w::key_event *>(why)) return false; auto ke=std::get<const x::w::key_event *>(why); if (ke->unicode != '\n') return false; if (!activated) return true; auto got=fields.get(); if (got) { auto & [sb, input_field, main_window] = *got; vi->set_volume(input_field, sb, main_window); } return true; }); checkbox->on_activate([vi, fields=x::make_weak_capture(sb, input_field)] (ONLY IN_THREAD, size_t state, const auto &ignore1, const auto &ignore2) { vi->use_decimals=state > 0; auto got=fields.get(); if (got) { auto &[sb, input_field]=*got; vi->set_decimals(input_field, sb); } }); } void testscrollbar() { x::destroy_callback::base::guard guard; auto close_flag=close_flag_ref::create(); auto main_window=x::w::main_window ::create([&] (const auto &main_window) { initialize_volume_control(main_window); main_window->set_window_title("Volume control"); main_window->show_all(); }); guard(main_window->connection_mcguffin()); main_window->on_disconnect([] { exit(1); }); main_window->on_delete ([close_flag] (ONLY IN_THREAD, const auto &ignore) { close_flag->close(); }); close_flag->wait(); } int main(int argc, char **argv) { try { testscrollbar(); } catch (const x::exception &e) { e->caught(); exit(1); } return 0; }
scrollbar.C
uses
a factory's create_horizontal_scrollbar
()'s to
create a new scroll-bar, and shows some examples of using it.
An x::w::scrollbar_config
parameter
specifies scroll-bar's configuration, and gets passed to
create_horizontal_scrollbar
() or
create_vertical_scrollbar
().
An existing scroll-bar's reconfigure
() method
updates its configuration.
create_horizontal_scrollbar
() and
create_vertical_scrollbar
() take the
following optional parameters. Either parameter is optional, but
they must appear in the following order if both are present:
The scroll-bar's initial
callback that gets executed to report changes to the scroll-bar's
position.
An existing scroll-bar's on_update
()
method installs a new callback, replacing the previous one.
A x::w::const_scrollbar_appearance
object that set a custom visual scrollbar
appearance.
x::w::scrollbar_config
's
minimum_size
sets the scroll-bar's minimum size,
in millimeters.
The scroll-bar's layout manager typically sizes the scroll-bar to
fit within its assigned cell, and this value instructs the layout
manager what the minimum size should be.
If scrollbar.C
does not specify its scrollbar's
width, it becomes the size of the short label above it.
As such, the scroll-bar's minimum width is slightly larger, and
the scrollbar.C
creates a larger window on account
of that.
Scroll-bars are focusable widgets, and they respond to the following keys when they have the keyboard focus:
Cursor-Left and
Cursor-Up keys move the scroll-bar towards
its left or upper end (smaller scroll-bar values).
Cursor-Right and
Cursor-Down keys move the scroll-bar towards
its right or lower end (larger scroll-bar values).
The scroll-bar's value gets adjusted by the
x::w::scrollbar_config
's
increment
;
in combination with the Ctrl key the scroll-bar's
value gets incremented or decremented by 1.
Page-Up and
Page-Down move the scroll-bar in the appropriate
direction.
The scroll-bar's value gets adjusted by the
x::w::scrollbar_config
's
page_size
.
It's possible for a
x::w::scrollbar_config
's
minimum_size
to be smaller than the
minimum size that's needed to meaningfully draw all of its
inner controls, subsequently the scroll-bars can get resized
to a very small size, accordingly.
When that happens the scroll-bars temporarily disable
themselves from receiving keyboard focus.
The reason for this is to avoid visual confusion when the
keyboard focus seems
to disappear (the scroll-bar's keyboard focus frame is so small
that it's hard to see or there may not be even enough pixels
to draw them).
The small scroll-bars remain disabled until their size is sufficient
to fully draw them, and they'll automatically reenable themselves.
This is combined with scroll-bars'
set_enabled
() method (that all
focusable widgets have). Scroll-bars have enabled keyboard
focus if they are both set_enabled
() and
are big enough for their drawn contents.