Chapter 40. Custom x::w::element widgets

Index

Resizing a custom widget

customelement.C is an example of creating a custom subclass of the x::w::element widget that draws a circle. The differences between creating a custom subclass of an x::w::element and a custom subclass of an x::w::canvas are:

Implementation classes

x::w::canvasObj's internal implementation class is x::w::canvasObj::implObj. x::w::elementObj's internal implementation class is x::w::elementObj::implObj but a custom subclass always uses an intermediate subclass, x::w::child_elementObj. x::w::child_elementObj is a subclass that represents an implementation class of a widget in its parent container. All custom widgets always get placed in a container, and therefore must derive from x::w::child_elementObj.

Element metrics

x::w::canvasObj::implObj's size comes from metrics in millimeters, or theme-specified metrics. The computed metrics include the current display theme's scaling factor. Adjusting the current display theme automatically updates the canvas's metrics, and the computed size. The canvas's metrics are set once when its implementation object gets created, and there are no provisions to modify it.

x::w::elementObj::implObj's metrics get specified in pixels, and they ignore the current display theme. The widget is resizable. This is done by updating its metrics. Afterwards, its container factors in the revised metrics, and may or may not resize the widget, accordingly.

customelement.C differs from customcanvas.C in several ways: it's a custom subclass of x::w::element; the custom widget's size is initially 50x50 pixels, and a button toggles its size between 50x50 and 100x100 pixels; the window's background color is set to white, overriding the current display theme's default; and the circle gets drawn using a fixed gradient color, going from black to light grey, also independent of the current display theme.

/*
** 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/main_window_appearance.H>
#include <x/w/gridlayoutmanager.H>
#include <x/w/gridfactory.H>
#include <x/w/element.H>
#include <x/w/button.H>

#include <x/w/impl/child_element.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 "customelement.examples.w.libcxx.com";
}

struct fore_color_tag; // Tag for the background_color_element

static inline auto create_child_element_init_params()
{
	x::w::child_element_init_params init_params;

	init_params.scratch_buffer_id=
		"my_element@customelement.examples.w.libcxx.com";

	init_params.initial_metrics={
		{50, 50, 50},
		{50, 50, 50}
	};

	return init_params;
}

class my_element_implObj : public x::w::scratch_and_mask_buffer_draw<
	x::w::background_color_elementObj<x::w::child_elementObj,
					  fore_color_tag>> {

	// Alias for the superclass.

	typedef x::w::scratch_and_mask_buffer_draw<
		x::w::background_color_elementObj<x::w::child_elementObj,
						  fore_color_tag>
		> superclass_t;
public:

	my_element_implObj(const x::w::container_impl &parent_container)
		: superclass_t{

		// Label ID for the scratch mask.
		        "my_element_mask@customelement.examples.w.libcxx.com",

		// Background color will be a linear gradient

			x::w::linear_gradient{
				0, 0, 1, 1,
				0, 0,
				{{0, {0, 0, 0}},
				 {1, {x::w::rgb::maximum/4*3,
				      x::w::rgb::maximum/4*3,
				      x::w::rgb::maximum/4*3}
				 }
				}
			},

		// Finally, the parameters to child_elementObjs constructor:
			parent_container,
			create_child_element_init_params()}
	{
	}

	~my_element_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;
};

typedef x::ref<my_element_implObj> my_element_impl;

void my_element_implObj::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)
{
	x::w::gc::base::properties props;

	props.function(x::w::gc::base::function::CLEAR);
	mask_gc->fill_rectangle(area_entire_rect, props);

	x::w::dim_t circle_width=area_entire_rect.width/10;
	x::w::dim_t circle_height=area_entire_rect.height/10;

	props.function(x::w::gc::base::function::SET);

	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)
	{
		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);

		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);
	}

	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);

	area_picture->composite(current_color_picture,
				mask_picture,
				0, 0,
				0, 0,
				0, 0,
				area_entire_rect.width,
				area_entire_rect.height,
				x::w::render_pict_op::op_over);
}

// "Public" object subclasses the public element object. Not really needed
// here, just for completeness sake.

class my_elementObj : public x::w::elementObj {

public:

	const my_element_impl impl; // My implementation object.

	// Constructor

	my_elementObj(const my_element_impl &impl)
		: x::w::elementObj{impl}, impl{impl}
	{
	}

	~my_elementObj()=default;
};

typedef x::ref<my_elementObj> my_element;

static void add_toggle_button(const my_element &e,
			      const x::w::factory &f)
{
	auto b=f->create_button("+/-", x::w::default_button());

	b->on_activate([e, s=x::w::dim_t{50}]
		       (ONLY IN_THREAD,
			const x::w::callback_trigger_t &trigger,
			const x::w::busy &mcguffin)
		       mutable
		       {
			       s=x::w::dim_t{150}-s;

			       e->impl->get_horizvert(IN_THREAD)
				       ->set_element_metrics(IN_THREAD,
							     {s, s, s},
							     {s, s, s});
		       });
}

void testcustomelement()
{
	x::destroy_callback::base::guard guard;

	auto close_flag=close_flag_ref::create();

	// Custom main window appearance, white background color

	x::w::main_window_config config;

	config.appearance=config.appearance->modify
		([]
		 (const x::w::main_window_appearance &appearance)
		 {
			 appearance->background_color=x::w::white;
		 });

	auto main_window=x::w::main_window::create
		(config,
		 [&]
		 (const auto &main_window)
		 {
			 main_window->remove_background_color();

			 auto layout=main_window->gridlayout();

			 layout->col_alignment(0, x::w::halign::center);

			 auto factory=layout->append_row();

			 // 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_element_impl::create(parent_container);

			 // Create the "public" object for the custom display
			 // element.
			 auto c=my_element::create(impl);

			 // Notify the factory that a new display element
			 // has been created, and it goes into its parent
			 // container.

			 factory->created_internally(c);

			 // Create a button to resize the element
			 // on the next row

			 factory=layout->append_row();

			 add_toggle_button(c, factory);
		 },
		 x::w::new_gridlayoutmanager{});

	main_window->set_window_title("Custom element");

	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 {
		testcustomelement();
	} catch (const x::exception &e)
	{
		e->caught();
		exit(1);
	}
	return 0;
}

This custom implementation subclass uses the same two mixin templates as customcanvas.C. But instead of initializing the background color template mixin with 0%, a reference to a theme background color, the background color template mixin gets initialized with a x::w::linear_gradient that defines a gradient color.

Although the gradient color is not theme-dependent, it does depend on the size of the widget. The background color widget mixin template takes care of recalculating the gradient color, automatically.

Resizing a custom widget

    x::w::child_element_init_params init_params;

    init_params.initial_metrics={
        {50, 50, 50},
        {50, 50, 50}
    };

The x::w::child_elementObj implementation class's constructor's parameter includes a initial_metrics member. Similar to x::w::canvasObj::implObj's analogous parameter, this x::w::horizvert_axi parameter sets the individual minimum, preferred, and maximum horizontal and vertical metrics of the new widget.

e->impl->get_horizvert(IN_THREAD)
    ->set_element_metrics(IN_THREAD,
              {s, s, s},
              {s, s, s});
	

A widget's size does not get adjusted directly. Instead, its horizontal and vertical metrics get adjusted. get_horizvert() returns the implementation object's metrics object. its set_element_metrics() method sets the widget's new horizontal and vertical metrics, expressed in pixels. The widget's container processes the updated metrics automatically and may (or may not) choose to actually resize the widget.

The widget's container has full responsibility for resizing the widget. The only thing that anyone else can do is update a widget's metrics, and hope for the best. Resized widgets get redrawn automatically. do_draw() in this example is materially the same as customcanvas.C's.