GtkFlowBox do NOT align in columns using horizontal orientation - gtk

In GTK I am trying to align widgets of different widths in a box such that they are next to each other in a line and flow into the next line if no more space is left. Basically I am trying to get similar results to Qt's Flow Layout as seen here:
(Source: This Qt documentation page)
I need each widget to be as small (width-wise) as possible, even allowing widths of only 20 pixels.
To automatically go to the next line if more space is needed, GTK provides the GtkFlowBox. My problem here is that it appears like the GtkFlowBox aligns all widgets in a grid of a dynamic width (cell-wise). The following image shows that even very wide widgets force widgets directly below it to use the exact same width:
(Mind that each "..." is in its own Label just like each text sequence.)
In the picture the second long label clearly results in the last label getting much more space than needed. The GTK documentation did not really make it clear to me that the GtkFlowBox works like a dynamic grid, but other images in the internet also show a grid-wise alignment everytime.
I am searching for a way where Widgets flow into the next row if there is no space left but all rows are independent from each other in other aspects. Qt appears to achieve this as the last button would contradict the widgets being aligned in a grid.
Is there a way achieving what I am trying with GtkFlowBox, or with another existing Layout, or only by manually implementing it? I think that the easiest manual way would be to use horizontal Boxes and dynamically map the widgets to different boxes so that no box is overfull. But clearly this would be drastically less easy and elegant than using an existing Layout.
(Note in case that anyone has a third-party solution written for specific environments: I am working with Rust (Gtk-rs))

The solution to my problem was implementing my own container. I want to try documenting how to approach this problem and will provide my code here.
The main concept is subclassing a container. This guide gives a good overview using C. The main ideas can easily be transferred to other languages if you inform yourself about subclassing in your language binding. There are two main problems to solve. One is the size-calculation needed for the container. The other one is how the children actually are aligned in the container.
Regarding the size-calculation, the request mode should be set to HeightForWidth as the width influences how many children fit into the rows and therefore also how many rows are needed. A problem here is that while larger widths imply smaller heights vice versa, gtk uses the minimum width to calculate the minimum height. This problem is documented in this post. I solved it by adding a property indicating how many children should be placed in one row at least such that the actual width can be approximated yielding acceptable height requirements. My get_preferred_width assumes that the minimum number of children is placed in each row to calculate the minimum width. A more natural approach would be to set it to the width of the largest child, but this results in a much larger minimum height. get_preferred_height_for_width directly depends on the allocation algorithm that it uses without really allocating size for its children.
I ran into some problems implementing the size allocation regarding minimum and natural sizes. It is not possible to use space remaining when using the minumum height evenly among the children to reach natural size in standard ways as children may flow to other rows at any time and the distribution shall be fair globally and not only per row. I assign the minimum width to each child and add a ratio of the width remaining to reach the natural width which is the same for all children. Due to reflowing, I found no nice algorithm to do this not being simple trial and error. Thus, I just try if the available height is enough for different ratios where I test those ratios down to a sufficiently low resolution. This yields a tradeoff between quality and efficiency. I use binary search to make it somehow efficient.
If you have any specific questions, feel free to ask. As code examples, I want to provide a third party solution in C which user James Westman proposed in my other post. I wanted to provide it as my own code is in Rust. Thus, it could be useful regarding the ideas while for instance the subclassing has a lot of differences to subclassing with gtk in C making it non-trivial just to use this code in other languages. My example also contains a dummy container allowing to test different natural and minumum sizes of the container's children. It works as standalone. Also mind that some functionality is not contained in the latest stable release of gtk-rs, use the newest version by e.g.
[dependencies.gtk]
git = "https://github.com/gtk-rs/gtk"
(Code in another answer as answer size is limited and I cannot share the git repo in the moment)

Read the description in another answer first. This code is excluded as the answer length is limited; the code and the text won't fit into one.
#[macro_use]
extern crate glib;
extern crate gdk;
extern crate gio;
extern crate gtk;
use gio::prelude::*;
use glib::subclass;
use glib::subclass::prelude::*;
use glib::translate::*;
use gtk::prelude::*;
mod dummy {
use super::*;
glib_wrapper! {
pub struct GAspectFillFrame(
Object<subclass::simple::InstanceStruct<internal::GAspectFillFrame>,
subclass::simple::ClassStruct<internal::GAspectFillFrame>,
SimpleAppWindowClass>)
#extends gtk::Bin, gtk::Container, gtk::Widget;
match fn {
get_type => || internal::GAspectFillFrame::get_type().to_glib(),
}
}
impl GAspectFillFrame {
pub fn new() -> GAspectFillFrame {
glib::Object::new(GAspectFillFrame::static_type(), &[])
.expect("Failed to create GAspectFillFrame instance")
.downcast::<GAspectFillFrame>()
.unwrap()
}
}
mod internal {
use super::*;
use gtk::subclass::{bin::BinImpl, container::ContainerImpl, widget::WidgetImpl};
use gtk::SizeRequestMode;
pub struct GAspectFillFrame {}
static PROPERTIES: [subclass::Property; 0] = [];
impl ObjectSubclass for GAspectFillFrame {
const NAME: &'static str = "GAspectFillFrame";
type ParentType = gtk::Bin;
type Instance = subclass::simple::InstanceStruct<Self>;
type Class = subclass::simple::ClassStruct<Self>;
glib_object_subclass!();
fn class_init(klass: &mut Self::Class) {
klass.install_properties(&PROPERTIES);
}
fn new() -> Self {
Self {}
}
}
impl ObjectImpl for GAspectFillFrame {
fn set_property(&self, _obj: &glib::Object, _id: usize, _value: &glib::Value) {
panic!();
}
fn get_property(&self, _obj: &glib::Object, _id: usize) -> Result<glib::Value, ()> {
panic!();
}
}
impl WidgetImpl for GAspectFillFrame {
fn get_preferred_width(&self, _widget: &gtk::Widget) -> (i32, i32) {
(50, 100)
}
fn get_preferred_height(&self, _widget: &gtk::Widget) -> (i32, i32) {
(50, 100)
}
fn get_request_mode(&self, _widget: &gtk::Widget) -> gtk::SizeRequestMode {
SizeRequestMode::ConstantSize
}
}
impl ContainerImpl for GAspectFillFrame {}
impl BinImpl for GAspectFillFrame {}
impl GAspectFillFrame {}
}
}
glib_wrapper! {
/// A container displaying its children rowwise and dynamically reflowing children to the next row
/// if there is no space left. In contrast to the `gtk::FlowBox` this container will not act like a grid and instead will
/// display its rows independent of each other besides of the assignment of the children to the rows.
///
/// The container allows to set the horizontal spacing between children and the vertical spacing between rows.
///
/// While the height of the container is queried for given widths as an increase in the latter decreases the height,
/// gtk uses the minimal width to calculate the minimal height. This could result in unexpected large heights.
/// This behaviour can be controlled by influencing the minimal width such that it is reasonably high.
/// To do so, set the `min-row-children` property. Using it to require a row to be able to contain a minimum of children which is
/// higher than the default value `1` leads to higher minimum widths and therefore also smaller minimum heights.
/// Note that the property is used for min width calculations but not actually enforced in size allocation.
/// It is a soft limit to allow an easier front to back calculation for the allocation.
/// Requiring a minimal number of children per line would imply new problems, as the minimum width calculation places a constant number
/// of elements in one line while the real allocation does more calculations allowing a non constant number.
/// Setting the min number of children as a hard limit could lead to situations where a for the min width calculations e.g. 4 children
/// are in each row while for the real allocation one row may have some remaining place for one more child than necessary.
/// In this case, the child would be in this row, the next row requires another child to reach the hard limit which could then overflow
/// the row if it had a large size.
/// Note that while the property is not enforced in final allocation, the min space requirements always are sufficient to place
/// all children with their min width or larger.
///
/// The container expands h-expand children with respect to the remaining space in their row which is evenly
/// distributed among all expanding children of that row.
///
/// In the case where the natural size requirements of the children cannot be satisfied, the container tries
/// to set their size such that they get their minimal size and an additional fraction/ratio of the difference between
/// their minimal and natural size such that this fraction is constant among all children.
/// Note that this operation is non-trivial as changes in this ratio may lead to children being shifted into
/// different rows and as rows may have some remaining space if others are full.
/// The implemented solution implements a tradeoff between a visually nice rescaling behaviour and efficiency.
pub struct ProperFlowBox(
Object<subclass::simple::InstanceStruct<internal::ProperFlowBox>,
subclass::simple::ClassStruct<internal::ProperFlowBox>,
SimpleAppWindowClass>)
#extends gtk::Container, gtk::Widget;
match fn {
get_type => || internal::ProperFlowBox::get_type().to_glib(),
}
}
impl ProperFlowBox {
/// Creates a new instance
pub fn new() -> ProperFlowBox {
glib::Object::new(ProperFlowBox::static_type(), &[])
.expect("Failed to create ProperFlowBox instance")
.downcast::<ProperFlowBox>()
.unwrap()
}
/// Sets the spacing between two children in a line and between children and the container's borders
///
/// # Arguments
///
/// * `spacing` - Horizontal spacing in pixels
pub fn set_h_spacing(&self, spacing: i32) {
self.set_property("h-spacing", &spacing)
.expect("Error setting h-spacing of ProperFlowBox");
}
/// Sets the spacing between two rows and between children and the container's borders
///
/// # Arguments
///
/// * `spacing` - Vertical spacing in pixels
pub fn set_v_spacing(&self, spacing: i32) {
self.set_property("v-spacing", &spacing)
.expect("Error setting v-spacing of ProperFlowBox");
}
/// Sets the minimum number of children used to approximate the required width
///
/// The set property is not enforced by the size allocation.
/// Setting this to higher values may result in less required minimum height of the container.
///
/// # Arguments
///
/// * `min_children` - Minimum number of children per row
pub fn set_min_row_children(&self, min_children: u32) {
self.set_property("min-row-children", &min_children)
.expect("Error setting min-row-children of ProperFlowBox");
}
}
mod internal {
use std::cell::RefCell;
use super::*;
use gtk::subclass::{
container::Callback,
container::ContainerImpl,
widget::{WidgetImpl, WidgetImplExt},
};
const DEFAULT_MIN_ROW_CHILDREN: u32 = 5;
pub struct ProperFlowBox {
children: RefCell<Vec<gtk::Widget>>,
h_spacing: RefCell<i32>,
v_spacing: RefCell<i32>,
min_row_children: RefCell<u32>,
}
static PROPERTIES: [subclass::Property; 3] = [
subclass::Property("h-spacing", |h_spacing| {
glib::ParamSpec::int(
h_spacing,
"Horizontal spacing",
"Space between two children in a row and between children and the container's borders",
0,
i32::MAX,
0,
glib::ParamFlags::READWRITE,
)
}),
subclass::Property("v-spacing", |v_spacing| {
glib::ParamSpec::int(
v_spacing,
"Vertical spacing",
"Space between two rows and between rows and the container's borders",
0,
i32::MAX,
0,
glib::ParamFlags::READWRITE,
)
}),
subclass::Property("min-row-children", |min_row_children| {
glib::ParamSpec::uint(
min_row_children,
"Minimal number of children in one row",
"Setting this to larger numbers increases the minumum width and decreases the minimum height",
1,
u32::MAX,
DEFAULT_MIN_ROW_CHILDREN,
glib::ParamFlags::READWRITE,
)
}),
];
impl ObjectSubclass for ProperFlowBox {
const NAME: &'static str = "ProperFlowBox";
type ParentType = gtk::Container;
type Instance = subclass::simple::InstanceStruct<Self>;
type Class = subclass::simple::ClassStruct<Self>;
glib_object_subclass!();
fn class_init(klass: &mut Self::Class) {
klass.install_properties(&PROPERTIES);
}
fn new() -> Self {
Self {
children: RefCell::new(Vec::new()),
h_spacing: RefCell::new(0),
v_spacing: RefCell::new(0),
min_row_children: RefCell::new(DEFAULT_MIN_ROW_CHILDREN),
}
}
}
impl ObjectImpl for ProperFlowBox {
fn set_property(&self, _obj: &glib::Object, id: usize, value: &glib::Value) {
let prop = &PROPERTIES[id];
match *prop {
subclass::Property("h-spacing", ..) => {
*self.h_spacing.borrow_mut() = value.get_some().unwrap();
}
subclass::Property("v-spacing", ..) => {
*self.v_spacing.borrow_mut() = value.get_some().unwrap();
}
subclass::Property("min-row-children", ..) => {
*self.min_row_children.borrow_mut() = value.get_some().unwrap();
}
_ => panic!("Tried to set unknown property of ProperFlowBox"),
}
}
fn get_property(&self, _obj: &glib::Object, id: usize) -> Result<glib::Value, ()> {
let prop = &PROPERTIES[id];
match *prop {
subclass::Property("h-spacing", ..) => Ok(self.h_spacing.borrow().to_value()),
subclass::Property("v-spacing", ..) => Ok(self.v_spacing.borrow().to_value()),
subclass::Property("min-row-children", ..) => {
Ok(self.min_row_children.borrow().to_value())
}
_ => panic!("Tried to get unknown property of ProperFlowBox"),
}
}
fn constructed(&self, obj: &glib::Object) {
self.parent_constructed(obj);
obj.downcast_ref::<gtk::Widget>()
.unwrap()
.set_has_window(false);
}
}
impl WidgetImpl for ProperFlowBox {
fn size_allocate(&self, widget: &gtk::Widget, allocation: &gtk::Allocation) {
self.parent_size_allocate(widget, &allocation);
// Search for maximal working natural ratio.
// Sort out most likely cases (enough for 1.0 or not for more than 0.0) and do
// binary search otherwise.
if self.check_height_for_natural_ratio(allocation.width, 1.0, false)
<= allocation.height
{
// Do 1.0
self.check_height_for_natural_ratio(allocation.width, 1.0, true);
} else if self.check_height_for_natural_ratio(allocation.width, 0.0, false)
>= allocation.height
{
// Do 0.0
self.check_height_for_natural_ratio(allocation.width, 0.0, true);
} else {
// Do binary search
// A lower stopping eps yields higher quality by finer transitions but less performance.
// 0.001 should be a reasonable tradeoff as binary search implies that ca. log_2(0.25/0.001) ~= 8 iterations.
let mut current_ratio = 0.5;
let mut current_step_width = 0.25;
const STOPPING_EPS: f64 = 0.001;
let mut max_ratio: f64 = 0.0;
while current_step_width > STOPPING_EPS {
let required_height =
self.check_height_for_natural_ratio(allocation.width, current_ratio, false);
if allocation.height >= required_height {
max_ratio = max_ratio.max(current_ratio);
current_ratio += current_step_width;
} else {
current_ratio -= current_step_width;
}
current_step_width /= 2.0;
}
self.check_height_for_natural_ratio(allocation.width, max_ratio, true);
}
}
fn get_request_mode(&self, _widget: &gtk::Widget) -> gtk::SizeRequestMode {
gtk::SizeRequestMode::HeightForWidth
}
fn get_preferred_height(&self, widget: &gtk::Widget) -> (i32, i32) {
widget.get_preferred_height_for_width(widget.get_preferred_width().0)
}
fn get_preferred_width_for_height(&self, widget: &gtk::Widget, _height: i32) -> (i32, i32) {
widget.get_preferred_width()
}
fn get_preferred_width(&self, _widget: &gtk::Widget) -> (i32, i32) {
// Calculate an approximation of the required width by exactly placing `min_row_children` many
// children in each row
let mut min_width = 0;
let mut natural_width = 0;
let mut current_min_width = 0;
let mut current_natural_width = 0;
for (index, child) in self
.children
.borrow()
.iter()
.filter(|c| c.is_visible())
.enumerate()
{
if index as u32 % *self.min_row_children.borrow() == 0 {
// Begin a new row
current_min_width = *self.h_spacing.borrow();
current_natural_width = *self.h_spacing.borrow();
}
current_min_width += child.get_preferred_width().0 + *self.h_spacing.borrow();
current_natural_width += child.get_preferred_width().1 + *self.h_spacing.borrow();
// Max each time for more consistent code as last row may not contain `min_row_children` children
min_width = min_width.max(current_min_width);
natural_width = natural_width.max(current_natural_width);
}
(min_width, natural_width)
}
fn get_preferred_height_for_width(&self, _widget: &gtk::Widget, width: i32) -> (i32, i32) {
(
self.check_height_for_natural_ratio(width, 0.0, false),
self.check_height_for_natural_ratio(width, 1.0, false),
)
}
}
impl ContainerImpl for ProperFlowBox {
fn add(&self, container: &gtk::Container, widget: &gtk::Widget) {
self.children.borrow_mut().push(widget.clone());
widget.set_parent(container);
if container.get_visible() {
container.queue_resize();
}
}
fn remove(&self, container: &gtk::Container, widget: &gtk::Widget) {
let index = self.children.borrow().iter().position(|c| c == widget);
if let Some(index) = index {
self.children.borrow_mut().remove(index);
widget.unparent();
} else {
println!("Tried to remove non-child from ProperFlowBox")
}
if container.get_visible() {
container.queue_resize();
}
}
fn forall(
&self,
_container: &gtk::Container,
_include_internals: bool,
callback: &Callback,
) {
// Need to deepcopy children as callbacks may also borrow children
let children = (*self.children.borrow()).clone();
for child in children.iter() {
callback.call(child);
}
}
}
impl ProperFlowBox {
/// Tries to fit the visible children for the given available width.
///
/// Given the available width, all visible children get their minimum size plus
/// the fraction defined by `natural_ratio` of the additional size bringing them to
/// their natural size. The function then returns the required height for the given width.
/// It is possible to directly call `size_allocate` for the visible children by enabling
/// it with the corresponding parameter. Real allocating also respects the `h-expand` property
/// of the children which does not influence the returned height of the function.
///
/// # Arguments
///
/// * `available_width` - Width available to be filled
/// * `natural_ratio` - Fraction of the additional size to meet the natural size coming from the minimum size.
/// For a ratio `x` the allocated width and height will be `min + x * (max - min) == (1 - x) * min + x * max`
/// * `allocate` - Call `size-allocate` on visible children if true
fn check_height_for_natural_ratio(
&self,
available_width: i32,
natural_ratio: f64,
allocate: bool,
) -> i32 {
// Coordinates of next child
let mut x = *self.h_spacing.borrow();
let mut y = *self.v_spacing.borrow();
let mut line_height = 0;
let mut number_row_children = 0;
let mut number_hexpand_row_children = 0;
let mut row_start_index = 0;
for (index, child) in self
.children
.borrow()
.iter()
.enumerate()
.filter(|(_, c)| c.is_visible())
{
let width = ProperFlowBox::get_barycentric_combination(
child.get_preferred_width(),
natural_ratio,
);
let height = ProperFlowBox::get_barycentric_combination(
match child.get_request_mode() {
gtk::SizeRequestMode::ConstantSize => child.get_preferred_height(),
gtk::SizeRequestMode::HeightForWidth
| gtk::SizeRequestMode::WidthForHeight => {
child.get_preferred_height_for_width(width)
}
_ => panic!("Unknown size request mode"),
},
natural_ratio,
);
if number_row_children > 0 && x + width + *self.h_spacing.borrow() > available_width
{
// Not enough space in current line => Go to next line
// Exception: Current child will be only one in line, then assign as there is no
// valid assignment to any line for this child
// Allocate finished line
if allocate {
self.allocate_row(
&self.children.borrow()[row_start_index..index],
y,
line_height,
number_hexpand_row_children,
available_width - x,
natural_ratio,
);
}
// Start next line
x = *self.h_spacing.borrow();
y += line_height + *self.v_spacing.borrow();
line_height = 0;
number_row_children = 0;
number_hexpand_row_children = 0;
row_start_index = index;
}
line_height = line_height.max(height);
x += width + *self.h_spacing.borrow();
number_row_children += 1;
if child.get_hexpand() {
number_hexpand_row_children += 1;
}
}
// Allocate last line
if allocate {
self.allocate_row(
&self.children.borrow()[row_start_index..],
y,
line_height,
number_hexpand_row_children,
available_width - x,
natural_ratio,
);
}
y + line_height + *self.v_spacing.borrow()
}
/// Allocates the size for the children in the given slice
///
/// # Arguments
///
/// * `children` - Slice of which the visible children inside form a row
/// * `y` - y coordinate of the row
/// * `height` - height of the row
/// * `number_hexpand_children` - Number of children with h-expand
/// * `remaining_space` - Unneeded space to be distributed among h-expand children
/// * `natural_ratio` - Fraction of the additional size to meet the natural size coming from the minimum size.
/// For a ratio `x` the allocated width and height will be `min + x * (max - min) == (1 - x) * min + x * max`
fn allocate_row(
&self,
children: &[gtk::Widget],
y: i32,
height: i32,
number_hexpand_children: i32,
remaining_space: i32,
natural_ratio: f64,
) {
let mut x = *self.h_spacing.borrow();
let additional_width_per_child = if number_hexpand_children > 0 {
remaining_space / number_hexpand_children
} else {
0
};
for child in children.iter().filter(|c| c.is_visible()) {
let mut width = ProperFlowBox::get_barycentric_combination(
child.get_preferred_width(),
natural_ratio,
);
if child.get_hexpand() {
width += additional_width_per_child;
}
child.size_allocate(&gtk::Allocation {
x,
y,
width,
height,
});
x += width + *self.h_spacing.borrow();
}
}
/// Returns the barycentric combination of `min` and `max` with the given ratio,
/// namely `min + ratio * (max - min) == (1 - ratio) * min + ratio * max`
///
/// # Arguments
///
/// * `min` - Min value
/// * `max` - Max value
/// * `ratio` - barycentric parameter, should be in `[0,1]` such that result is in `[min,max]`
fn get_barycentric_combination((min, max): (i32, i32), ratio: f64) -> i32 {
((1.0 - ratio) * min as f64 + ratio * max as f64) as i32
}
}
}
fn main() {
let application = gtk::Application::new(None, Default::default())
.expect("Failed to initialize GTK application");
application.connect_activate(|app| {
let window = gtk::ApplicationWindow::new(app);
window.set_title("Custom FlowBox Demo");
let style = ".bordered {border: 1px solid black;}";
let provider = gtk::CssProvider::new();
provider
.load_from_data(style.as_bytes())
.expect("Failed to load CSS");
gtk::StyleContext::add_provider_for_screen(
&gdk::Screen::get_default().expect("Failed to load css provider"),
&provider,
gtk::STYLE_PROVIDER_PRIORITY_APPLICATION,
);
let flow = ProperFlowBox::new();
flow.set_h_spacing(10);
flow.set_v_spacing(10);
for i in 1..15 {
let dummy = dummy::GAspectFillFrame::new();
let label = gtk::Label::new(Some("Hello world"));
dummy.add(&label);
if i % 3 == 0 {
dummy.set_hexpand(true);
}
label.get_style_context().add_class("bordered");
flow.add(&dummy);
}
window.add(&flow);
window.show_all();
});
application.run(&[]);
}

The solution I found for this problem on my program was to:
Have the FlowBox inside a regular Box oriented horizontally
Set that box's hexpand property to TRUE
Set to 2 the minimum amount of items per line of the flow box
I had to do things this way because setting the FlowBox hexpand to TRUE would make its items to get more spacing the more I resized the window. And if had I set the minimum amount of items per line to 1, then I would have always one item per line.
Doing things the way I described make the FlowBox to have as many items per line as there is space available in the window, while having no extra space added between items when increasing the window. To be really honest, I do not know why this worked.
What I was doing in my program was to create radio buttons for options, and get the buttons to flow to the next line once there is not enough space in the horizontal. Here are the relevant parts of my code (in C):
// Create the wrapper box to hold the entry's widgets (name label and options)
GtkWidget *my_wrapper = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, ENTRY_VERTICAL_SPACING);
gtk_widget_set_hexpand(my_wrapper, TRUE);
// Create a name label with the string on the text buffer
GtkWidget *my_name_label = gtk_label_new(text_buffer);
gtk_widget_set_valign(my_name_label, GTK_ALIGN_START);
gtk_widget_set_margin_end(my_name_label, ENTRY_HORIZONTAL_SPACING);
gtk_widget_set_margin_top(my_name_label, 8); // Added some top margin so the label's text align with the options
// (for whatever reason, just aligning both to the top was not enough)
// Create a flow box for the options
GtkWidget *my_options = gtk_flow_box_new();
gtk_widget_set_valign(my_options, GTK_ALIGN_START);
gtk_flow_box_set_min_children_per_line(GTK_FLOW_BOX(my_options), 2);
// Add the flow box to the wrapper
gtk_container_add(GTK_CONTAINER(my_wrapper), my_options);
// Radio button
GtkWidget *my_radio_button = NULL;
GtkWidget *previous_button = NULL;
// Loop for creating and adding the radio buttons
/* for/while (some condition) */
{
char *my_text = /* Option's text according to the condition */ ;
my_radio_button = gtk_radio_button_new_with_label(NULL, my_text);
gtk_radio_button_join_group(GTK_RADIO_BUTTON(my_radio_button), GTK_RADIO_BUTTON(previous_button));
previous_button = my_radio_button;
gtk_container_add(GTK_CONTAINER(my_options), my_radio_button);
}

Related

Hw to Freeze the Button in Scroll bar

I am using unity 2018.3.7. In my project I have instantiate 25 buttons in the scroll bar..I am scrolling the button. It is working well.
Actually i want freeze the 12 the button.When i scroll the button. The 12 th button should be constantly should be there and other button should scroll go up.
scrolling should be done but the freeze button should be constantly there.
Like Excel. If we freeze the top row. The top row is constantly there and scrolling is happened.
Like that i have to do in unity.
How to Freeze the button in scroll bar.
Edit:
Actually I have uploaded new gif file. In that gif file 2 row is freezed (Row Heading1,Row Heading2, Row Heading3,RowHeading4).
2nd row is constantly there. Rest of the the rows 4 to 100 rows are going up.
Like that i have to do ...
How can i do it..
Though your question still is very broad I guess I got now what you want. This will probably not be exactly the solution you want since your question is quite vague but it should give you a good idea and starting point for implementing it in the way you need it.
I can just assume Unity UI here is the setup I would use. Since it is quite complex I hope this image will help to understand
so what do we have here:
Canvas
has the RowController script attached. Here reference the row prefab and adjust how many rows shall be added
Panel is the only child of Canvas. I just used it as a wrapper for having a custom padding etc - it's optional
FixedRowPanel
Here we will add the fixed rows on runtime
Initially has a height of 0!
Uses anchore pivot Y = 1! This is later important for changing the height on runtime
Uses a Vertical Layout Group for automatically arranging added children
ScrollView - Your scrollview as you had it but
Uses streched layout to fill the entire Panel (except later the reduced space for the fixed rows)
Uses anchor pivot Y = 1! Again important for changing the height and position on runtime later
Viewport afaik it should already use streched anchors by default but not sure so make it
Content
Uses a Vertical Layout Group
Initially has a height of 0 (but I set this in the code anyway) and will grow and shrink accordingly when adding and removing rows
And finally RowPrefab
I didn't add its hierachy in detail but it should be clear. It has a Toggle and a Text as childs ;)
Has the Row script attached we use for storing and getting some infos
Now to the scripts - I tried to comment everything
The Row.cs is quite simple
public class Row : MonoBehaviour
{
// Reference these via the Inspector
public Toggle FixToggle;
public Text FixTogText;
public Text RowText;
public RectTransform RectTransform;
// Will be set by RowController when instantiating
public int RowIndex;
private void Awake()
{
if (!RectTransform) RectTransform = GetComponent<RectTransform>();
if (!FixToggle) FixToggle = GetComponentInChildren<Toggle>(true);
if (!FixTogText) FixTogText = FixToggle.GetComponentInChildren<Text>(true);
}
}
And here is the RowController.cs
public class RowController : MonoBehaviour
{
public Row RowPrefab;
public RectTransform ScrollView;
public RectTransform Content;
public RectTransform FixedRowParent;
public int HowManyRows = 24;
public List<Row> CurrentlyFixedRows = new List<Row>();
public List<Row> CurrentlyScrolledRows = new List<Row>();
// Start is called before the first frame update
private void Start()
{
// initially the content has height 0 since it has no children yet
Content.sizeDelta = new Vector2(Content.sizeDelta.x, 0);
for (var i = 0; i < HowManyRows; i++)
{
// Create new row instances and set their values
var row = Instantiate(RowPrefab, Content);
// store the according row index so we can later sort them on it
row.RowIndex = i;
row.RowText.text = $"Row Number {i + 1}";
// add a callback for the Toggle
row.FixToggle.onValueChanged.AddListener(s => HandleToggleChanged(row, s));
// increase the content's size to fit the children
// if you are using any offset/padding between them
// you will have to add it here as well
Content.sizeDelta += Vector2.up * row.RectTransform.rect.height;
// don't forget to add them to this list so we can easily access them
CurrentlyScrolledRows.Add(row);
}
}
// called every time a row is fixed or unfixed via the Toggle
private void HandleToggleChanged(Row row, bool newState)
{
if (newState)
{
// SET FIXED
// Update the button text
row.FixTogText.text = "Unfix";
// Move this row to the fixedRow panel
row.transform.SetParent(FixedRowParent);
// be default we assume we want the first position
var targetIndex = 0;
// if there are other fixed rows already find the first child of FixedRowParent that has a bigger value
if (CurrentlyFixedRows.Count > 0) targetIndex = CurrentlyFixedRows.FindIndex(r => r.RowIndex > row.RowIndex);
// handle case when no elements are found -> -1
// this means this row is the biggest and should be the last item
if (targetIndex < 0) targetIndex = CurrentlyFixedRows.Count;
// and finally in the hierachy move it to that position
row.transform.SetSiblingIndex(targetIndex);
// insert it to the fixed list and remove it from the scrolled list
CurrentlyFixedRows.Insert(targetIndex, row);
CurrentlyScrolledRows.Remove(row);
// Make the fixed Panel bigger about the height of one row
FixedRowParent.sizeDelta += Vector2.up * row.RectTransform.rect.height;
// Make both the scrollView and Content smaller about one row
Content.sizeDelta -= Vector2.up * row.RectTransform.rect.height;
ScrollView.sizeDelta -= Vector2.up * row.RectTransform.rect.height;
// Move the scrollView down about one row in order to make space for the fixed panel
ScrollView.anchoredPosition -= Vector2.up * row.RectTransform.rect.height;
}
else
{
// SET UNFIXED - Basically the same but the other way round
// Update the button text
row.FixTogText.text = "Set Fixed";
// Move this row back to the scrolled Content
row.transform.SetParent(Content);
// be default we assume we want the first position
var targetIndex = 0;
// if there are other scrolled rows already find the first child of Content that has a bigger value
if (CurrentlyScrolledRows.Count > 0) targetIndex = CurrentlyScrolledRows.FindIndex(r => r.RowIndex > row.RowIndex);
// handle case when no elements are found -> -1
// this means this row is the biggest and should be the last item
if (targetIndex < 0) targetIndex = CurrentlyScrolledRows.Count;
// and finally in the hierachy move it to that position
row.transform.SetSiblingIndex(targetIndex);
// insert it to the scrolled list
CurrentlyScrolledRows.Insert(targetIndex, row);
// and remove it from the fixed List
CurrentlyFixedRows.Remove(row);
// shrink the fixed Panel about ne row height
FixedRowParent.sizeDelta -= Vector2.up * row.RectTransform.rect.height;
// Increase both Content and Scrollview height by one row
Content.sizeDelta += Vector2.up * row.RectTransform.rect.height;
ScrollView.sizeDelta += Vector2.up * row.RectTransform.rect.height;
// Move scrollView up about one row height to fill the empty space
ScrollView.anchoredPosition += Vector2.up * row.RectTransform.rect.height;
}
}
}
Result:
As you can see I can now fix and unfix rows dynamically while keeping their correct order within both according panels.

Accessing CSV row when using a class

*Edited with advise from below. STill working on minisming the code more. *
I am attempting to shift to using classes in my code. I have this functioning in a larger piece of code and I'm attempting to recreate the stepping action now using a class.
if (i< table.getRowCount()) {
row = table.getRow(i);
k=row.getInt("y");
x=row.getInt("x");
}
//draw white ellipse visual
ellipse(x*diam, y, diam, diam);
// step next circle up
y+=Step;
// -- if off the set upper limit of the line,
// swing right
if (frameCount%(k/Step)==0) {
x+=Step;
y=starty;
// increment row counter
i=i+1;
}
I am trying to troubleshoot why I am unable to step through variable i as the rows in the csv table.
I am expecting k to print out the data in column named "y" in the csv file.
I am expecting i to print out a number incrementing by 1 each time variable y reaches the value of k.
From printing out my variables i,k,x
k is successfully getting the data from csv file column named "y"
x is successfully getting the data from csv file column named "x"
y is successfully incrementing
i is not incrementing
y is not registering it has reached k.
my thoughts right now are that actually variable k is a marker for y to stop incrementing but should perhaps not be part of the Dot parameters in the constructor.
// An Array of Dot objects
Dot[] dots;
// A Table object
Table table;
float diam=10.0;
int i;
void setup() {
size(560, 420);
loadData();
background(0);
// set the animation speed
frameRate(10);
}
void draw() {
// Display all dots
for (Dot d : dots) {
d.display();
d.move();
}
}
void loadData() {
// Load CSV file into a Table object
// "header" option indicates the file has a header row
table = loadTable("data.csv", "header");
// The size of the array of Dot objects is determined by the total number of rows in the CSV
dots = new Dot[table.getRowCount()];
// You can access iterate over all the rows in a table
int rowCount = 0;
for (TableRow row : table.rows()) {
// You can access the fields via their column name (or index)
if (i< table.getRowCount()) {
row = table.getRow(i);
float x = row.getFloat("x");
float k = row.getFloat("y");
float d = row.getFloat("diameter");
//String n = row.getString("name");
// Make a fot object out of the data read
dots[rowCount] = new Dot(i,k, x, d);
rowCount++;
}
}
// check to see if at end of data
if (i == table.getRowCount()) {
i = 0;//if so, loop back to first row
}
}
class Dot {
float x =10.0;
float y=10.0;
float diam=10.0;
float k;
int step=10;
int startx =10;
int starty=10;
float i=0;
Dot(float i_,float k_, float x_,float diam_) {
k=k_;
x=x_;
i=i_;
diam=diam_;
}
void display() {
noStroke();
fill(255);
// draw a circle
ellipse(x*diam, y, diam, diam);
}
void move() {
// move next circle to the right
y+=step;
// -- if off the right edge of the line,
// swing down and all the way to the left
if (frameCount%(k/step)==0) {
x+=step;
y=starty;
// increment row counter
i=i+1;
}
// if off the bottom of the page, stop
if (x>=width) {
fill(0);
text("done", width/2, height/2);
noLoop();
}
}
}

How can I determine the screen size using addon SDK?

How can I get the screen size using with addon SDK ?
var w = screen.width/2;
gives me an error : Message: ReferenceError: screen is not defined
You can use the window you have associated to your add-on; it's probably safer, because it will work even if the last visible window is closed but firefox is still opened (e.g. on OS X):
const { window: { screen }} = require("sdk/addon/window");
console.log(screen.width);
This will work:
var screen = require('sdk/window/utils').getMostRecentBrowserWindow().screen;
console.log(screen.width);
If you want multi monitor support I have a script but you have to understand it. It uses XPCOM, and it needs a range. This script only checks along the x axis, you should also check along the y axis.
So this is the script here that will detect all monitors in the x plane IF it falls in the y plane of 0-20 coordintates of primary screen, I don't recommend this method.
var sm = Cc['#mozilla.org/gfx/screenmanager;1'].getService(Ci.nsIScreenManager);
function getScreens() {
var screen = null;
var screens = [];
var screenManager = sm;
var min = 0;
var max = 0;
for (x = 0; x < 15000; x += 600) {
var s = screenManager.screenForRect(x, 20, 10, 10);
if (s != screen) {
screen = s;
var left = {},
top = {},
width = {},
height = {};
screenManager.primaryScreen.GetRect(left, top, width, height);
screens.push({
width: width.value,
height: height.value,
min: min,
max: min + width.value
});
min += width.value;
}
}
return screens;
}
var screens = getScreens();
console.log('screens:', screens);
This is the method I recommend
I needed to detect all monitor dimensons and had to resort to jsctypes, if you need that its here: https://github.com/Noitidart/NativeShot/blob/master/modules/workers/MainWorker.js#L853-L1523
That code is extremely long, thats because its getting all monitors and the taking screenshots of them. So you will want to extract just the monitors part. If you need help with it I can do it for you.

Eliminate sudden additions/deletions in D3 line chart transition

You can see this code in action here: http://bl.ocks.org/2626142
This code draws a line chart, then transitions between 3 sample data sets. When moving from a small data set to a larger one, the extra data points suddenly appear instead of smoothly unfolding from the existing line.
When moving from a larger data set to a smaller one, the line is suddenly truncated before transitioning to fill the whole chart.
With this code there are sudden additions and deletions to the line and gridlines. How do I eliminate those?
var data = [
[0,2,3,2,8],
[2,4,1,5,3],
];
var data2 = [
[0,1,2,3,4,5],
[9,8,7,6,5,6],
];
var data3 = [
[1,3,2],
[0,8,5],
];
var w = 300,
h = 100;
var chart = d3.select('body').append('div')
.attr('class', 'chart')
.append('svg:svg')
.attr('width', w)
.attr('height', h);
var color = d3.scale.category10();
function drawdata(data, chart) {
var num = data[0].length-1;
var x = d3.scale.linear().domain([0, num]).range([0,w]);
var y = d3.scale.linear().domain([0, 10]).range([h, 0]);
var line = d3.svg.line()
.x(function(d, i) { return x(i); })
.y(function(d) { return y(d); });
var flat = d3.svg.line()
.x(function(d, i) { return x(i); })
.y(y(-1));
var lines = chart.selectAll('.line')
.data(data);
lines.enter().append('path')
.attr('class', 'line')
.style('stroke', function(d,i) { return color(i); })
.attr('d', line);
lines.transition()
.ease('linear')
.duration(500)
.attr('d', line);
lines.exit().remove();
// legend
var ticks = chart.selectAll('line')
.data(x.ticks(num));
ticks.enter().append('line')
.attr('x1', x)
.attr('x2', x)
.attr('y1', 0)
.attr('y2', h)
.attr('class', 'rule');
ticks.transition()
.ease('linear')
.duration(500)
.attr('x1', x)
.attr('x2', x)
.attr('y1', 0)
.attr('y2', h);
ticks.exit().remove();
}
var dats = [data, data2, data3];
function next() {
var it = dats.shift();
dats.push(it);
drawdata(it, chart);
}
setInterval(next, 2000);
next();
I faced a similar problem recently, and solved it using a custom interpolator for paths:
// Add path interpolator to d3
d3.interpolators.push(function(a, b) {
var isPath, isArea, interpolator, ac, bc, an, bn;
// Create a new array of a given length and fill it with the given value
function fill(value, length) {
return d3.range(length)
.map(function() {
return value;
});
}
// Extract an array of coordinates from the path string
function extractCoordinates(path) {
return path.substr(1, path.length - (isArea ? 2 : 1)).split('L');
}
// Create a path from an array of coordinates
function makePath(coordinates) {
return 'M' + coordinates.join('L') + (isArea ? 'Z' : '');
}
// Buffer the smaller path with coordinates at the same position
function bufferPath(p1, p2) {
var d = p2.length - p1.length;
// Paths created by d3.svg.area() wrap around such that the 'end'
// of the path is in the middle of the list of coordinates
if (isArea) {
return fill(p1[0], d/2).concat(p1, fill(p1[p1.length - 1], d/2));
} else {
return fill(p1[0], d).concat(p1);
}
}
// Regex for matching the 'd' attribute of SVG paths
isPath = /M-?\d*\.?\d*,-?\d*\.?\d*(L-?\d*\.?\d*,-?\d*\.?\d*)*Z?/;
if (isPath.test(a) && isPath.test(b)) {
// A path is considered an area if it closes itself, indicated by a trailing 'Z'
isArea = a[a.length - 1] === 'Z';
ac = extractCoordinates(a);
bc = extractCoordinates(b);
an = ac.length;
bn = bc.length;
// Buffer the ending path if it is smaller than the first
if (an > bn) {
bc = bufferPath(bc, ac);
}
// Or, buffer the starting path if the reverse is true
if (bn > an) {
ac = bufferPath(ac, bc);
}
// Create an interpolater with the buffered paths (if both paths are of the same length,
// the function will end up being the default string interpolator)
interpolator = d3.interpolateString(bn > an ? makePath(ac) : a, an > bn ? makePath(bc) : b);
// If the ending value changed, make sure the final interpolated value is correct
return bn > an ? interpolator : function(t) {
return t === 1 ? b : interpolator(t);
};
}
});
Here's what the original gist looks like with the new interpolator: http://bl.ocks.org/4535474.
Its approach is to 'buffer' the smaller dataset's path by inserting zero-length line segments at the beginning. The effect is that new segments expand out of a single point at the start of the line, and unused segments similarly collapse down to a single point.
Transitioning between datasets of different sizes (apparently) isn't a common problem, and doesn't have a universal solution. Because I was visualizing time-series data and transitioning between daily/weekly/monthly intervals, I needed the segments towards the end of the path to maintain visual continuity. I can imagine a case in which you'd want to do the same for the beginning of the path, or perhaps expand/contract the path by uniformly buffering segments throughout. Either way the same approach will work.

Having a dynamic number of standalone views divide the space evenly

When showing multiple instances of the same view, I would like the following to hold true:
The views do not stack
The number of them is dynamic (let's say 1 to 10) and not known initially: they are added by the user, one by one
They are positioned above/below each other
They take up all the vertical space amongst themselves
By default they have equal height (if there are 3 views, each gets 1/3 of the total vertical space, adding a 4th view causes them all to get 1/4 etc)
What I use to add the views in Perspective is the following:
public void createInitialLayout(IPageLayout layout) {
String editorArea = layout.getEditorArea();
layout.addStandaloneViewPlaceholder(MarkerView.VIEW_ID + ":0", IPageLayout.TOP, 0.6F, editorArea, true);
layout.addStandaloneViewPlaceholder(MarkerView.VIEW_ID + ":1", IPageLayout.BOTTOM, 0.6F, editorArea, true);
layout.addStandaloneViewPlaceholder(MarkerView.VIEW_ID + ":2", IPageLayout.BOTTOM, 0.6F, editorArea, true);
The problem is that not knowing what the number of views will be, the ratios won't provide equal space to all instances of the view.
Is it somehow possible to adjust the ratios of existing views after adding a new one?
I've also tried having the view class implement ISizeProvider (which I admit I do not have a full grasp of), but haven't managed to achieve what I want with that either:
#Override
public int getSizeFlags(boolean width) {
return width ? 0 : SWT.FILL | SWT.MIN | SWT.MAX;
}
#Override
public int computePreferredSize(boolean width, int availableParallel, int availablePerpendicular, int preferredResult) {
int windowHeight = PlatformUI.getWorkbench().getDisplay().getActiveShell().getBounds().height;
int nrOfViews = Model.getInstance().markerCounter;
return width ? preferredResult : windowHeight / nrOfViews;
}
Is this approach something in the right direction at least?