x::w::canvas
Index
customcanvas.C
shows an example of creating
a custom subclass of the
x::w::canvas
widget that draws a circle. This example demonstrates:
Canvas's implementation object class's,
x::w::canvasObj::implObj
's
constructor takes a parameter that sets its minimum,
preferred, and maximum size (as well as the optional background
color). This is the only widget in
its window, so the window becomes resizable within the specified
constraints.
LibCXXW's internal header files define several
“mixin templates”. Each such template creates
a class that inherits from its template parameter.
This class is usually a
subclass of
x::w::child_elementObj
.
The template either directly overrides any methods itself,
or also multiply inherits from a
mixin class; then
overrides or defines methods inherited from its
x::w::child_elementObj
superclass using
the mixin-provided functionality.
This example uses two mixin templates that implement a simplified interface for drawing the contents of the widget, and specifying a theme-based color for drawing this circle.
LibCXXW implements several classes that provide access to the
graphic context, pixmap, and picture resources of the underlying
X protocol and its RENDER extension.
customcanvas.C
uses them to draw a circle
in the custom widget.
/* ** Copyright 2018-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/appid.H> #include <x/w/main_window.H> #include <x/w/gridlayoutmanager.H> #include <x/w/gridfactory.H> #include <x/w/canvas.H> #include <x/w/button_event.H> #include <x/w/impl/canvas.H> #include <x/w/impl/background_color_element.H> #include <x/w/impl/scratch_and_mask_buffer_draw.H> #include <x/w/impl/container.H> #include <string> #include <iostream> #include "close_flag.H" std::string x::appid() noexcept { return "customcanvas.examples.w.libcxx.com"; } // Implementation object derives from the canvas element implementation object. // // Mixin templates: // // background_color_elementObj: attaches a background_color to the // implementation object, the color to draw the circle with. // // scratch_and_mask_buffer_draw: simplified element drawing. Draw the entire // element, and provide an additional masking pixmap. struct fore_color_tag; // Tag for the background_color_element class my_canvas_implObj : public x::w::scratch_and_mask_buffer_draw< x::w::background_color_elementObj<x::w::canvasObj::implObj, fore_color_tag>> { // Alias for the superclass. typedef x::w::scratch_and_mask_buffer_draw< x::w::background_color_elementObj<x::w::canvasObj::implObj, fore_color_tag> > superclass_t; static constexpr double default_minimum=20, default_preferred=50, default_maximum=100; double minimum=default_minimum; double preferred=default_preferred; double maximum=default_maximum; public: using x::w::canvasObj::implObj::update; // Construction needs to take care of several details. // 1) Prepare a canvas_init_params for canvasObj::implObj's constructor, // that specifies the size of the display element, in millimeters // // 2) It would be nice if we could construct the masking scratch buffer // specifying the dimensions we expect to have. The preferred // element size is specified as 50x50 millimeters, and the scratch // buffer wants the size specified in pixels. // // Well, the canvas constructor computes the initial metrics for the // display element in pixels, so we can use this to initialize the // scratch buffer mixin. // // Start the ball rolling by constructing the canvas_init_params my_canvas_implObj(const x::w::container_impl &parent_container) : my_canvas_implObj { parent_container, x::w::canvas_init_params{ // Horizontal metrics: {default_minimum, default_preferred, default_maximum}, // Vertical metrics: {default_minimum, default_preferred, default_maximum}, // Label ID for the custom // element's scratch buffer. "my_canvas@customcanvas.examples.w.libcxx.com" } } { } // Now, we use x::w::canvasObj::implObj::create_child_element_params // to compute the metrics of the display elements in pixels. my_canvas_implObj(const x::w::container_impl &parent_container, const x::w::canvas_init_params &init_params) : my_canvas_implObj{parent_container, init_params, create_child_element_params(parent_container, init_params)} { } // Now we have all the info we need. my_canvas_implObj(const x::w::container_impl &parent_container, const x::w::canvas_init_params &init_params, const x::w::child_element_init_params &child_init_params) : superclass_t{ // scratch_and_mask_buffer_draw mixin constructor params: // // Label ID for the scratch mask. "my_canvas_mask@customcanvas.examples.w.libcxx.com", // The background_color_element mixin constructor param: // The circle's color. Take color "0%" from the theme. "0%", // Finally, the parameters to canvasObj::implObj's constructor: parent_container, init_params, child_init_params} { } ~my_canvas_implObj()=default; // Implement do_draw(). void do_draw(ONLY IN_THREAD, const x::w::draw_info &di, const x::w::picture &area_picture, const x::w::pixmap &area_pixmap, const x::w::gc &area_gc, const x::w::picture &mask_picture, const x::w::pixmap &mask_pixmap, const x::w::gc &mask_gc, const x::w::clip_region_set &clipped, const x::w::rectangle &area_entire_rect) override; // Override process_button_event(). bool process_button_event(ONLY IN_THREAD, const x::w::button_event &be, xcb_timestamp_t timestamp) override; }; typedef x::ref<my_canvas_implObj> my_canvas_impl; // Draw our custom canvas display element. void my_canvas_implObj::do_draw(ONLY IN_THREAD, const x::w::draw_info &di, // The picture, pixmap, and the graphic context // for the drawing area. const x::w::picture &area_picture, const x::w::pixmap &area_pixmap, const x::w::gc &area_gc, // The picture, pixmap, and the graphic context // for the 1-bit masking buffer. const x::w::picture &mask_picture, const x::w::pixmap &mask_pixmap, const x::w::gc &mask_gc, // A handle indicating that the drawing area // is ready, and clipped. const x::w::clip_region_set &clipped, // Give the current size of the display element. // // x & y are always 0, and width+height gives // the element's current size, in pixels. const x::w::rectangle &area_entire_rect) { // We are responsible for clearing the masking buffer. x::w::gc::base::properties props; props.function(x::w::gc::base::function::CLEAR); mask_gc->fill_rectangle(area_entire_rect, props); // The circle's size is 1/10th the size of the display element. x::w::dim_t circle_width=area_entire_rect.width/10; x::w::dim_t circle_height=area_entire_rect.height/10; // Draw a filled in circle, then clear our the center of the circle. // We end up with the mask to draw the border with. // // Then we'll reduce the radius by twice the circle_width/height, and // clear the inner part of the circle. props.function(x::w::gc::base::function::SET); // Fill in a circle in the rectangle that, basically, encompasses our // entire drawing area. mask_gc->fill_arc(0, 0, area_entire_rect.width, area_entire_rect.height, 0, 360*64, props); if (circle_width > 0 && circle_height > 0) { // Subtract circle_width/height*2 from the element's size. // // This gives the width/height of the inner size of the circle. x::w::dim_t inner_width=area_entire_rect.width -circle_width-circle_width; x::w::dim_t inner_height=area_entire_rect.height -circle_height-circle_height; props.function(x::w::gc::base::function::CLEAR); // So we now clear a circle in a rectangle whose top-left // corner is (circle_width, circle_height), and whose size is // inner_width x inner_height. // // The library uses type-safe integer types like dim_t and // coord_t that are not only type-safe, but also guard against // overflows. Which is the reason why, above, the width/height // get subtracted twice, instead of subtracting width/height*2 // (the multiplication results in a larger integer type. // // The first two parameters to fill_arc are coord_t-s, so // need to use truncate() in order to pass the dim_t-s. mask_gc->fill_arc(x::w::coord_t::truncate(circle_width), x::w::coord_t::truncate(circle_height), inner_width, inner_height, 0, 360*64, props); } // Now retrieve the circle_color x::w::background_color current_color= x::w::background_color_element<fore_color_tag>::get(IN_THREAD); x::w::const_picture current_color_picture= current_color->get_current_color(IN_THREAD); // And use the mask to composite the circle into the scratch buffer, // which is already filled in with the default background color. area_picture->composite(current_color_picture, mask_picture, 0, 0, // src_x, src_y 0, 0, // mask_x, mask_y 0, 0, // dst_x, dst_y area_entire_rect.width, area_entire_rect.height, x::w::render_pict_op::op_over); } ///////////////////////////////////////////////////////////////////// // // An implementation object overrides process_button_event() in order to // to do something with button clicks. bool my_canvas_implObj::process_button_event(ONLY IN_THREAD, const x::w::button_event &be, xcb_timestamp_t timestamp) { // If the overriden process_button_event() does not wish to process // this button event: it should invoke the overriden method. // Take action on pointer button #1 clicks. activate_for() is just // a fancy check for a button press, as opposed to a button release, // event. if (!activate_for(be) || be.button != 1) return superclass_t::process_button_event(IN_THREAD, be, timestamp); // In response to button #1 clicks we toggle the element metrics // between minimum/preferred/maximum of 20/50/100 and 110/140/150. // // Our container will oblige and resize us accordingly. Note that // after the first resize-ment, subsequent ones result in very little // adjustment. This is because the largest metric's minimum size // is 100. When we flip back to 20/50/100, since the maximum is 100 // our size only gets reduced slightly to 100, from 110. minimum=130-minimum; preferred=190-preferred; maximum=250-maximum; update(IN_THREAD, {minimum, preferred, maximum}, {minimum, preferred, maximum}); return true; } // "Public" object subclasses the public canvas object. Not really needed // here, just for completeness sake. class my_canvasObj : public x::w::canvasObj { public: const my_canvas_impl impl; // My implementation object. // Constructor my_canvasObj(const my_canvas_impl &impl) : x::w::canvasObj{impl}, impl{impl} { } ~my_canvasObj()=default; }; typedef x::ref<my_canvasObj> my_canvas; void testcustomcanvas() { 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) { auto layout=main_window->gridlayout(); auto factory=layout->append_row(); factory->padding(0); // Obtain the parent container from the factory. x::w::container_impl parent_container= factory->get_container_impl(); // Create the "implementation" object for the custom // display element. auto impl=my_canvas_impl::create(parent_container); // Create the "public" object for the custom display // element. auto c=my_canvas::create(impl); // Notify the factory that a new display element // has been created, and it goes into its parent // container. factory->created_internally(c); }, x::w::new_gridlayoutmanager{}); main_window->set_window_title("Custom canvas"); guard(main_window->connection_mcguffin()); main_window->on_disconnect([] { _exit(1); }); main_window->on_delete ([close_flag] (THREAD_CALLBACK, const auto &ignore) { close_flag->close(); }); main_window->show_all(); close_flag->wait(); } int main(int argc, char **argv) { try { testcustomcanvas(); } catch (const x::exception &e) { e->caught(); exit(1); } return 0; }
x::w::canvasObj::implObj
's
constructor first parameter is a reference to the new widget's
parent container. Its second parameter is an
x::w::canvas_init_params
object.
The object's first two members specify the new widget's metrics:
the horizontal metric and the vertical metric.
They are specified as a
x::w::dim_axis_arg
values.
A horizontal or a vertical metric consists of three discrete values: minimum, preferred, and maximum; and the metric specifies that the widget's width or height should have this minimum, preferred, or maximum size. The widget's container should size the widget to at least its minimum width, or height; but no larger than the maximum value. The preferred metric requests the widget's initial width and height; however in a container with multiple widgets it's unlikely that the container will make everyone happy.
Widgets do not get explicitly resized, directly. This is done indirectly by updating their metrics. Their container and its layout manager always have the final word on each widget's actual size.
Its possible that a container resizes a widget to be larger
than its maximum requested metric. For example, the
grid layout manager can
be instructed to use
or
x::w::halign::fill
for an widget's alignment,
which may exceed the widget's maximum metric.
x::w::valign::fill
It's unlikely that a container will size an widget to be smaller than its minimum metrics. However, a new widget's width or height is usually 0, until it gets formally sized by its container. So it's possible that a widget's state indicates that its smaller than its minimum metric; this can also happen in some edge cases when the parent container has no other options.
In conclusion: custom widgets must be prepared to draw themselves when their size is outside of their declared metrics. Widgets should still reasonably draw their contents if their size exceeds their maximum metrics. It is acceptable to simply draw an empty widget if their size is smaller than their minimum metrics.
x::w::dim_arg
specifies the value of a minimum, preferred, or maximum metric.
The value may be:
A text string. This references a width or height defined by the current display theme, by name. An exception gets thrown if the theme does not define the width/height with this label.
A double
value specifies the width or
height in millimeters. The actual pixel size gets computed based
on the display screen's resolution, and scaled by the current theme
scaling factor.
Additionally, a NAN
value for a maximum metric,
and only the maximum metric, sets
an unlimited maximum metric. The widget has no defined
maximum width or height.
/* ... */ x::w::canvas_init_params{ {20.0, 50.0, 100.0}, {20.0, 50.0, 100.0}, "my_canvas@examples.w.libcxx.com"}
customcanvas.C
creates horizontal and vertical metrics that set the custom canvas's
minimum width and height to 20 millimeters, the preferred width and
height to 50 millimeters, and 100 millimeters as the maximum metric.
This is the only widget in the window, so the resulting
window is resizable within these bounds.
Replacing the 100 millimeter value with NAN
results
in a window that has no upper maximum width or height.
x::w::canvasObj::implObj
major selling point, as a candidate for a custom subclass, is that its
size gets specified in millimeters and does not depend on the display
screen's resolution. Furthermore, it
automatically updates its metrics, in realtime, if the
current display theme scaling factor gets adjusted by
cxxwtheme.
If the window's size ends up being outside the minimum or maximum
metrics because of the new theme scaling factor,
the window gets resized accordingly.