Chapter 11. Input field spinners

Index

Implementing spin buttons
Input field spinner.

Two optional, small buttons to the right of an input field provide a convenient way to enter a number in a relatively small numerical range. The two small buttons "spin" the value in the input field; decreasing or increasing the value with each click.

spininput.C gives an example of using spin buttons:

/*
** Copyright 2018-2021 Double Precision, Inc.
** See COPYING for distribution information.
*/

#include "config.h"
#include "close_flag.H"

#include <x/exception.H>
#include <x/destroy_callback.H>
#include <x/appid.H>

#include <x/w/main_window.H>
#include <x/w/gridlayoutmanager.H>
#include <x/w/gridfactory.H>
#include <x/w/label.H>
#include <x/w/font_literals.H>
#include <x/w/input_field.H>
#include <x/w/container.H>
#include "spininput.H"

#include <string>
#include <iostream>
#include <sstream>
#include <optional>

std::string x::appid() noexcept
{
	return "spininput.examples.w.libcxx.com";
}

void create_mainwindow(const x::w::main_window &main_window,
		       const close_flag_ref &close_flag,
		       const options &opts)
{
	auto layout=main_window->gridlayout();

	layout->row_alignment(0, x::w::valign::middle);

	x::w::gridfactory factory=layout->append_row();

	factory->create_label("Enter a number, 1-49:");

	x::w::input_field_config config{3};

	config.alignment=x::w::halign::right;
	config.maximum_size=2;

	// There are two ways to enable spin controls in an input field.

	if (!opts.custom->value)
	{
		// Using set_default_spin_control_factories() creates
		// spin control using a default, theme-specified appearance.

		config.set_default_spin_control_factories();
	}
	else
	{
		// Alternatively, set_spin_control_factories() takes two
		// callbacks parameters. Each callback receives a factory
		// as a parameter, and must create exactly one display
		// element that becomes one of the spin controls.
		//
		// This example creates two plain labels: a "-" and a "+".


		config.set_spin_control_factories
			([](const x::w::factory &factory) {
				factory->create_label({
						"liberation mono"_font,
							"-"

							});
			}, [](const x::w::factory &factory) {
				factory->create_label({
						"liberation mono"_font,
							"+"

							});
			});
	}

	auto field=factory->create_input_field("", config);

	// set_default_spin_factories()/set_spin_control_factories()
	// creates the spin controls. They're just there, they do nothing
	// by themselves.
	//
	// So first, we'll attach a validator for the input field, to
	// make the input field accept only numbers between 1 and 49.

	auto validated_int=field->set_string_validator<int>(
		[]
		(ONLY IN_THREAD,
		 const std::string &value,
		 std::optional<int> &parsed_value,
		 x::w::input_lock &lock,
		 const x::w::callback_trigger_t &trigger)
		{
			if (parsed_value)
			{
				if (*parsed_value > 0 && *parsed_value <= 49)
					return;
			}
			else
			{
				if (value.empty())
				{
					lock.stop_message("Entry required");
					return;
				}
			}

			parsed_value.reset();
			lock.stop_message("Must enter a number 1-49");
		},
		[]
		(int n)
		{
			return std::to_string(n);
		}
	);

	// Now that we have our validated input field, we can make use of
	// it to drive the spin buttons.
	//
	// on_spin() installs two callbacks, one for each button.
	//
	// The first callback spins the value in the input field down by 1,
	// the second one spins it up by 1.
	//
	// The input field holds references on both their validator callbacks
	// and spin callbacks. For that reason the spin callbacks cannot
	// capture either because this would create a circular reference.
	//
	// What spin callbacks can do is capture the validators' "contents"
	// member, which implements value, value_or(), and an IN_THREAD-only
	// set(). This is sufficient for the spin callbacks to read the
	// current value and update it.

	field->on_spin([contents=validated_int->contents]
		       (ONLY IN_THREAD,
			x::w::input_lock &lock,
			const x::w::callback_trigger_t &trigger,
			const x::w::busy &mcguffin)
		       {
			       auto value=contents->value_or(1);

			       if (--value)
				       contents->set(IN_THREAD, lock, value);
		       },
		       [contents=validated_int->contents]
		       (ONLY IN_THREAD,
			x::w::input_lock &lock,
			const x::w::callback_trigger_t &trigger,
			const x::w::busy &mcguffin)
		       {
			       auto value=contents->value_or(0);

			       if (++value < 50)
				       contents->set(IN_THREAD, lock, value);
		       });
}

void spininputs(const options &opts)
{
	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)
		 {
			 create_mainwindow(main_window, close_flag, opts);
		 });

	main_window->on_disconnect([]
				   {
					   _exit(1);
				   });

	guard(main_window->connection_mcguffin());

	main_window->set_window_title("Spin!");
	main_window->on_delete
		([close_flag]
		 (ONLY IN_THREAD,
		  const x::w::busy &ignore)
		 {
			 close_flag->close();
		 });

	main_window->show_all();

	close_flag->wait();
}

int main(int argc, char **argv)
{
	try {
		options opts;

		opts.parse(argc, argv);
		spininputs(opts);
	} catch (const x::exception &e)
	{
		e->caught();
		exit(1);
	}
	return 0;
}

Spin buttons get enabled by using a x::w::input_field_config object with create_input_field(). x::w::input_field_config set_default_spin_control_factories() sets the factories that create two spin buttons with a default appearance: a - and a + icon. The expectation is that these buttons decrement and increment the value in the input field.

Alternatively, set_spin_control_factories() installs callbacks that specify custom factories for the spin buttons. Passing a --custom option to spininput.C uses it to create spin buttons as plain text - and + labels.

Implementing spin buttons

The spin buttons, by themselves, don't do anything to the input field. It's up to the application to implement them. The spin buttons don't have to increment the numerical value in the input field by 1. The input field is not required to have a numerical value, and the spin buttons may update the input field in some other fashion. The application has complete control over what the spin buttons do. on_spin() installs two callbacks, one for each spin button, and the callbacks have complete flexibility to update the contents of the input field.

spininput.C is an example of a typical imeplementation: input field validators. spininput.C installs a validator that requires a number value in the input field, rejects invalid input, and automatically converts the entered text into an ordinary int. on_spin()'s callbacks simply need to increment or decrement the int value, within bounds, and use the validator to update the input field.

Note

on_spin() callbacks must follow specific rules to avoid creating circular reference when using input field validators.