Merge branch 'latch' into 'master'
Latch views See merge request Librem5/squeekboard!416
This commit is contained in:
		@ -52,6 +52,8 @@ buttons:
 | 
				
			|||||||
            locking:
 | 
					            locking:
 | 
				
			||||||
                lock_view: "upper_accents"
 | 
					                lock_view: "upper_accents"
 | 
				
			||||||
                unlock_view: "accents"
 | 
					                unlock_view: "accents"
 | 
				
			||||||
 | 
					                looks_locked_from:
 | 
				
			||||||
 | 
					                    - "upper"
 | 
				
			||||||
        outline: "altline"
 | 
					        outline: "altline"
 | 
				
			||||||
        icon: "key-shift"
 | 
					        icon: "key-shift"
 | 
				
			||||||
    BackSpace:
 | 
					    BackSpace:
 | 
				
			||||||
@ -94,6 +96,8 @@ buttons:
 | 
				
			|||||||
            locking:
 | 
					            locking:
 | 
				
			||||||
                lock_view: "upper_accents"
 | 
					                lock_view: "upper_accents"
 | 
				
			||||||
                unlock_view: "upper"
 | 
					                unlock_view: "upper"
 | 
				
			||||||
 | 
					                looks_locked_from:
 | 
				
			||||||
 | 
					                    - "accents"
 | 
				
			||||||
        outline: "altline"
 | 
					        outline: "altline"
 | 
				
			||||||
        label: "ĄĘ"
 | 
					        label: "ĄĘ"
 | 
				
			||||||
    period:
 | 
					    period:
 | 
				
			||||||
 | 
				
			|||||||
@ -31,11 +31,16 @@ sq_button.wide {
 | 
				
			|||||||
    border-color: #3e3a44;
 | 
					    border-color: #3e3a44;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
sq_button.locked {
 | 
					sq_button.latched {
 | 
				
			||||||
    background: #ffffff;
 | 
					    background: #ffffff;
 | 
				
			||||||
    color: #2b292f;
 | 
					    color: #2b292f;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					sq_button.locked {
 | 
				
			||||||
 | 
					    background: #ffffff;
 | 
				
			||||||
 | 
					    color: #1c71d8;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
sq_button.action {
 | 
					sq_button.action {
 | 
				
			||||||
    font-size: 0.75em;
 | 
					    font-size: 0.75em;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -34,11 +34,16 @@ sq_button.wide {
 | 
				
			|||||||
    border-color: @borders; /* #3e3a44; */
 | 
					    border-color: @borders; /* #3e3a44; */
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
sq_button.locked {
 | 
					sq_button.latched {
 | 
				
			||||||
    background: @theme_fg_color; /*#ffffff;*/
 | 
					    background: @theme_fg_color; /*#ffffff;*/
 | 
				
			||||||
    color: @theme_bg_color; /*#2b292f;*/
 | 
					    color: @theme_bg_color; /*#2b292f;*/
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					sq_button.locked {
 | 
				
			||||||
 | 
					    background: @theme_fg_color; /*#ffffff;*/
 | 
				
			||||||
 | 
					    color: mix(@theme_selected_bg_color, @theme_bg_color, 0.4); /*#2b292f;*/
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
sq_button.action {
 | 
					sq_button.action {
 | 
				
			||||||
    font-size: 0.75em;
 | 
					    font-size: 0.75em;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -18,8 +18,6 @@
 | 
				
			|||||||
 * 02110-1301 USA
 | 
					 * 02110-1301 USA
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include "config.h"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include <math.h>
 | 
					#include <math.h>
 | 
				
			||||||
#include <string.h>
 | 
					#include <string.h>
 | 
				
			||||||
#include <gdk-pixbuf/gdk-pixbuf.h>
 | 
					#include <gdk-pixbuf/gdk-pixbuf.h>
 | 
				
			||||||
@ -33,10 +31,6 @@
 | 
				
			|||||||
static void render_button_label (cairo_t *cr, GtkStyleContext *ctx,
 | 
					static void render_button_label (cairo_t *cr, GtkStyleContext *ctx,
 | 
				
			||||||
                                                const gchar *label, EekBounds bounds);
 | 
					                                                const gchar *label, EekBounds bounds);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void eek_render_button                         (EekRenderer *self,
 | 
					 | 
				
			||||||
                                                cairo_t     *cr, const struct squeek_button *button,
 | 
					 | 
				
			||||||
                                                gboolean     pressed, gboolean locked);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
static void
 | 
					static void
 | 
				
			||||||
render_outline (cairo_t     *cr,
 | 
					render_outline (cairo_t     *cr,
 | 
				
			||||||
                GtkStyleContext *ctx,
 | 
					                GtkStyleContext *ctx,
 | 
				
			||||||
@ -60,21 +54,21 @@ render_outline (cairo_t     *cr,
 | 
				
			|||||||
        position.x, position.y, position.width, position.height);
 | 
					        position.x, position.y, position.width, position.height);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static void render_button_in_context(gint scale_factor,
 | 
					/// Rust interface
 | 
				
			||||||
 | 
					void eek_render_button_in_context(uint32_t scale_factor,
 | 
				
			||||||
                                     cairo_t     *cr,
 | 
					                                     cairo_t     *cr,
 | 
				
			||||||
                                     GtkStyleContext *ctx,
 | 
					                                     GtkStyleContext *ctx,
 | 
				
			||||||
                                     const struct squeek_button *button) {
 | 
					                                     EekBounds bounds,
 | 
				
			||||||
 | 
					                                     const char *icon_name,
 | 
				
			||||||
 | 
					                                     const gchar *label) {
 | 
				
			||||||
    /* blank background */
 | 
					    /* blank background */
 | 
				
			||||||
    cairo_set_source_rgba (cr, 0.0, 0.0, 0.0, 0.0);
 | 
					    cairo_set_source_rgba (cr, 0.0, 0.0, 0.0, 0.0);
 | 
				
			||||||
    cairo_paint (cr);
 | 
					    cairo_paint (cr);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    EekBounds bounds = squeek_button_get_bounds(button);
 | 
					 | 
				
			||||||
    render_outline (cr, ctx, bounds);
 | 
					    render_outline (cr, ctx, bounds);
 | 
				
			||||||
    cairo_paint (cr);
 | 
					    cairo_paint (cr);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /* render icon (if any) */
 | 
					    /* render icon (if any) */
 | 
				
			||||||
    const char *icon_name = squeek_button_get_icon_name(button);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (icon_name) {
 | 
					    if (icon_name) {
 | 
				
			||||||
        cairo_surface_t *icon_surface =
 | 
					        cairo_surface_t *icon_surface =
 | 
				
			||||||
            eek_renderer_get_icon_surface (icon_name, 16, scale_factor);
 | 
					            eek_renderer_get_icon_surface (icon_name, 16, scale_factor);
 | 
				
			||||||
@ -104,25 +98,27 @@ static void render_button_in_context(gint scale_factor,
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const gchar *label = squeek_button_get_label(button);
 | 
					 | 
				
			||||||
    if (label) {
 | 
					    if (label) {
 | 
				
			||||||
        render_button_label (cr, ctx, label, squeek_button_get_bounds(button));
 | 
					        render_button_label (cr, ctx, label, bounds);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void
 | 
					/// Prepare context for drawing the button.
 | 
				
			||||||
eek_render_button (EekRenderer *self,
 | 
					/// The context MUST be released using the corresponing "put" procedure
 | 
				
			||||||
            cairo_t     *cr,
 | 
					/// before drawing the next button.
 | 
				
			||||||
            const struct squeek_button *button,
 | 
					/// Interface for Rust.
 | 
				
			||||||
               gboolean     pressed,
 | 
					GtkStyleContext *
 | 
				
			||||||
               gboolean     locked)
 | 
					eek_get_style_context_for_button (EekRenderer *self,
 | 
				
			||||||
 | 
					                                  const char *name,
 | 
				
			||||||
 | 
					                                  const char *outline_name,
 | 
				
			||||||
 | 
					                                  const char *locked_class,
 | 
				
			||||||
 | 
					               uint64_t     pressed)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    GtkStyleContext *ctx = self->button_context;
 | 
					    GtkStyleContext *ctx = self->button_context;
 | 
				
			||||||
    /* Set the name of the button on the widget path, using the name obtained
 | 
					    /* Set the name of the button on the widget path, using the name obtained
 | 
				
			||||||
       from the button's symbol. */
 | 
					       from the button's symbol. */
 | 
				
			||||||
    g_autoptr (GtkWidgetPath) path = NULL;
 | 
					    g_autoptr (GtkWidgetPath) path = NULL;
 | 
				
			||||||
    path = gtk_widget_path_copy (gtk_style_context_get_path (ctx));
 | 
					    path = gtk_widget_path_copy (gtk_style_context_get_path (ctx));
 | 
				
			||||||
    const char *name = squeek_button_get_name(button);
 | 
					 | 
				
			||||||
    gtk_widget_path_iter_set_name (path, -1, name);
 | 
					    gtk_widget_path_iter_set_name (path, -1, name);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /* Update the style context with the updated widget path. */
 | 
					    /* Update the style context with the updated widget path. */
 | 
				
			||||||
@ -131,19 +127,22 @@ eek_render_button (EekRenderer *self,
 | 
				
			|||||||
       (pressed) or normal. */
 | 
					       (pressed) or normal. */
 | 
				
			||||||
    gtk_style_context_set_state(ctx,
 | 
					    gtk_style_context_set_state(ctx,
 | 
				
			||||||
        pressed ? GTK_STATE_FLAG_ACTIVE : GTK_STATE_FLAG_NORMAL);
 | 
					        pressed ? GTK_STATE_FLAG_ACTIVE : GTK_STATE_FLAG_NORMAL);
 | 
				
			||||||
    const char *outline_name = squeek_button_get_outline_name(button);
 | 
					    if (locked_class) {
 | 
				
			||||||
    if (locked) {
 | 
					        gtk_style_context_add_class(ctx, locked_class);
 | 
				
			||||||
        gtk_style_context_add_class(ctx, "locked");
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    gtk_style_context_add_class(ctx, outline_name);
 | 
					    gtk_style_context_add_class(ctx, outline_name);
 | 
				
			||||||
 | 
					    return ctx;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    render_button_in_context(self->scale_factor, cr, ctx, button);
 | 
					/// Interface for Rust.
 | 
				
			||||||
 | 
					void eek_put_style_context_for_button(GtkStyleContext *ctx,
 | 
				
			||||||
 | 
					                                      const char *outline_name,
 | 
				
			||||||
 | 
					                                      const char *locked_class) {
 | 
				
			||||||
    // Save and restore functions don't work if gtk_render_* was used in between
 | 
					    // Save and restore functions don't work if gtk_render_* was used in between
 | 
				
			||||||
    gtk_style_context_set_state(ctx, GTK_STATE_FLAG_NORMAL);
 | 
					    gtk_style_context_set_state(ctx, GTK_STATE_FLAG_NORMAL);
 | 
				
			||||||
    gtk_style_context_remove_class(ctx, outline_name);
 | 
					    gtk_style_context_remove_class(ctx, outline_name);
 | 
				
			||||||
    if (locked) {
 | 
					    if (locked_class) {
 | 
				
			||||||
        gtk_style_context_remove_class(ctx, "locked");
 | 
					        gtk_style_context_remove_class(ctx, locked_class);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -328,6 +327,11 @@ eek_renderer_set_scale_factor (EekRenderer *renderer, gint scale)
 | 
				
			|||||||
    renderer->scale_factor = scale;
 | 
					    renderer->scale_factor = scale;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Rust interface.
 | 
				
			||||||
 | 
					uint32_t eek_renderer_get_scale_factor(EekRenderer *renderer) {
 | 
				
			||||||
 | 
					    return renderer->scale_factor;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
cairo_surface_t *
 | 
					cairo_surface_t *
 | 
				
			||||||
eek_renderer_get_icon_surface (const gchar *icon_name,
 | 
					eek_renderer_get_icon_surface (const gchar *icon_name,
 | 
				
			||||||
                               gint size,
 | 
					                               gint size,
 | 
				
			||||||
 | 
				
			|||||||
@ -90,13 +90,5 @@ struct transformation {
 | 
				
			|||||||
    gdouble scale;
 | 
					    gdouble scale;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
struct squeek_button;
 | 
					 | 
				
			||||||
struct squeek_row;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/// Represents the path to the button within a view
 | 
					 | 
				
			||||||
struct button_place {
 | 
					 | 
				
			||||||
    const struct squeek_row *row;
 | 
					 | 
				
			||||||
    const struct squeek_button *button;
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
G_END_DECLS
 | 
					G_END_DECLS
 | 
				
			||||||
#endif  /* EEK_TYPES_H */
 | 
					#endif  /* EEK_TYPES_H */
 | 
				
			||||||
 | 
				
			|||||||
@ -10,7 +10,7 @@ pub struct KeySym(pub String);
 | 
				
			|||||||
type View = String;
 | 
					type View = String;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Use to send modified keypresses
 | 
					/// Use to send modified keypresses
 | 
				
			||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
 | 
					#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
 | 
				
			||||||
pub enum Modifier {
 | 
					pub enum Modifier {
 | 
				
			||||||
    /// Control and Alt are the only modifiers
 | 
					    /// Control and Alt are the only modifiers
 | 
				
			||||||
    /// which doesn't interfere with levels,
 | 
					    /// which doesn't interfere with levels,
 | 
				
			||||||
@ -30,6 +30,11 @@ pub enum Action {
 | 
				
			|||||||
        lock: View,
 | 
					        lock: View,
 | 
				
			||||||
        /// When unlocked by pressing it or emitting a key
 | 
					        /// When unlocked by pressing it or emitting a key
 | 
				
			||||||
        unlock: View,
 | 
					        unlock: View,
 | 
				
			||||||
 | 
					        /// Whether key has a latched state
 | 
				
			||||||
 | 
					        /// that pops when another key is pressed.
 | 
				
			||||||
 | 
					        latches: bool,
 | 
				
			||||||
 | 
					        /// Should take on *locked* appearance whenever latch comes back to those views.
 | 
				
			||||||
 | 
					        looks_locked_from: Vec<View>,
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    /// Hold this modifier for as long as the button is pressed
 | 
					    /// Hold this modifier for as long as the button is pressed
 | 
				
			||||||
    ApplyModifier(Modifier),
 | 
					    ApplyModifier(Modifier),
 | 
				
			||||||
@ -49,14 +54,24 @@ pub enum Action {
 | 
				
			|||||||
impl Action {
 | 
					impl Action {
 | 
				
			||||||
    pub fn is_locked(&self, view_name: &str) -> bool {
 | 
					    pub fn is_locked(&self, view_name: &str) -> bool {
 | 
				
			||||||
        match self {
 | 
					        match self {
 | 
				
			||||||
            Action::LockView { lock, unlock: _ } => lock == view_name,
 | 
					            Action::LockView { lock, unlock: _, latches: _, looks_locked_from: _ } => lock == view_name,
 | 
				
			||||||
 | 
					            _ => false,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    pub fn has_locked_appearance_from(&self, locked_view_name: &str) -> bool {
 | 
				
			||||||
 | 
					        match self {
 | 
				
			||||||
 | 
					            Action::LockView { lock: _, unlock: _, latches: _, looks_locked_from } => {
 | 
				
			||||||
 | 
					                looks_locked_from.iter()
 | 
				
			||||||
 | 
					                    .find(|view| locked_view_name == view.as_str())
 | 
				
			||||||
 | 
					                    .is_some()
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
            _ => false,
 | 
					            _ => false,
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    pub fn is_active(&self, view_name: &str) -> bool {
 | 
					    pub fn is_active(&self, view_name: &str) -> bool {
 | 
				
			||||||
        match self {
 | 
					        match self {
 | 
				
			||||||
            Action::SetView(view) => view == view_name,
 | 
					            Action::SetView(view) => view == view_name,
 | 
				
			||||||
            Action::LockView { lock, unlock: _ } => lock == view_name,
 | 
					            Action::LockView { lock, unlock: _, latches: _, looks_locked_from: _ } => lock == view_name,
 | 
				
			||||||
            _ => false,
 | 
					            _ => false,
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										14
									
								
								src/data.rs
									
									
									
									
									
								
							
							
						
						
									
										14
									
								
								src/data.rs
									
									
									
									
									
								
							@ -289,7 +289,13 @@ struct ButtonMeta {
 | 
				
			|||||||
#[serde(deny_unknown_fields)]
 | 
					#[serde(deny_unknown_fields)]
 | 
				
			||||||
enum Action {
 | 
					enum Action {
 | 
				
			||||||
    #[serde(rename="locking")]
 | 
					    #[serde(rename="locking")]
 | 
				
			||||||
    Locking { lock_view: String, unlock_view: String },
 | 
					    Locking {
 | 
				
			||||||
 | 
					        lock_view: String,
 | 
				
			||||||
 | 
					        unlock_view: String,
 | 
				
			||||||
 | 
					        pops: Option<bool>,
 | 
				
			||||||
 | 
					        #[serde(default)]
 | 
				
			||||||
 | 
					        looks_locked_from: Vec<String>,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    #[serde(rename="set_view")]
 | 
					    #[serde(rename="set_view")]
 | 
				
			||||||
    SetView(String),
 | 
					    SetView(String),
 | 
				
			||||||
    #[serde(rename="show_prefs")]
 | 
					    #[serde(rename="show_prefs")]
 | 
				
			||||||
@ -600,7 +606,9 @@ fn create_action<H: logging::Handler>(
 | 
				
			|||||||
            )
 | 
					            )
 | 
				
			||||||
        ),
 | 
					        ),
 | 
				
			||||||
        SubmitData::Action(Action::Locking {
 | 
					        SubmitData::Action(Action::Locking {
 | 
				
			||||||
            lock_view, unlock_view
 | 
					            lock_view, unlock_view,
 | 
				
			||||||
 | 
					            pops,
 | 
				
			||||||
 | 
					            looks_locked_from,
 | 
				
			||||||
        }) => ::action::Action::LockView {
 | 
					        }) => ::action::Action::LockView {
 | 
				
			||||||
            lock: filter_view_name(
 | 
					            lock: filter_view_name(
 | 
				
			||||||
                name,
 | 
					                name,
 | 
				
			||||||
@ -614,6 +622,8 @@ fn create_action<H: logging::Handler>(
 | 
				
			|||||||
                &view_names,
 | 
					                &view_names,
 | 
				
			||||||
                warning_handler,
 | 
					                warning_handler,
 | 
				
			||||||
            ),
 | 
					            ),
 | 
				
			||||||
 | 
					            latches: pops.unwrap_or(true),
 | 
				
			||||||
 | 
					            looks_locked_from,
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        SubmitData::Action(
 | 
					        SubmitData::Action(
 | 
				
			||||||
            Action::ShowPrefs
 | 
					            Action::ShowPrefs
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										210
									
								
								src/drawing.rs
									
									
									
									
									
								
							
							
						
						
									
										210
									
								
								src/drawing.rs
									
									
									
									
									
								
							@ -3,20 +3,24 @@
 | 
				
			|||||||
use cairo;
 | 
					use cairo;
 | 
				
			||||||
use std::cell::RefCell;
 | 
					use std::cell::RefCell;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use ::action::Action;
 | 
					use ::action::{ Action, Modifier };
 | 
				
			||||||
use ::keyboard;
 | 
					use ::keyboard;
 | 
				
			||||||
use ::layout::{ Button, Layout };
 | 
					use ::layout::{ Button, Label, LatchedState, Layout };
 | 
				
			||||||
use ::layout::c::{ EekGtkKeyboard, Point };
 | 
					use ::layout::c::{ Bounds, EekGtkKeyboard, Point };
 | 
				
			||||||
use ::submission::Submission;
 | 
					use ::submission::Submission;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use glib::translate::FromGlibPtrNone;
 | 
					use glib::translate::FromGlibPtrNone;
 | 
				
			||||||
use gtk::WidgetExt;
 | 
					use gtk::WidgetExt;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use std::collections::HashSet;
 | 
				
			||||||
 | 
					use std::ffi::CStr;
 | 
				
			||||||
 | 
					use std::ptr;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
mod c {
 | 
					mod c {
 | 
				
			||||||
    use super::*;
 | 
					    use super::*;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    use cairo_sys;
 | 
					    use cairo_sys;
 | 
				
			||||||
    use std::os::raw::c_void;
 | 
					    use std::os::raw::{ c_char, c_void };
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    // This is constructed only in C, no need for warnings
 | 
					    // This is constructed only in C, no need for warnings
 | 
				
			||||||
    #[allow(dead_code)]
 | 
					    #[allow(dead_code)]
 | 
				
			||||||
@ -24,17 +28,44 @@ mod c {
 | 
				
			|||||||
    #[derive(Clone, Copy)]
 | 
					    #[derive(Clone, Copy)]
 | 
				
			||||||
    pub struct EekRenderer(*const c_void);
 | 
					    pub struct EekRenderer(*const c_void);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // This is constructed only in C, no need for warnings
 | 
				
			||||||
 | 
					    /// Just don't clone this for no reason.
 | 
				
			||||||
 | 
					    #[allow(dead_code)]
 | 
				
			||||||
 | 
					    #[repr(transparent)]
 | 
				
			||||||
 | 
					    #[derive(Clone, Copy)]
 | 
				
			||||||
 | 
					    pub struct GtkStyleContext(*const c_void);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    extern "C" {
 | 
					    extern "C" {
 | 
				
			||||||
        // Button and View inside CButtonPlace are safe to pass to C
 | 
					 | 
				
			||||||
        // as long as they don't outlive the call
 | 
					 | 
				
			||||||
        // and nothing dereferences them
 | 
					 | 
				
			||||||
        #[allow(improper_ctypes)]
 | 
					        #[allow(improper_ctypes)]
 | 
				
			||||||
        pub fn eek_render_button(
 | 
					        pub fn eek_renderer_get_scale_factor(
 | 
				
			||||||
            renderer: EekRenderer,
 | 
					            renderer: EekRenderer,
 | 
				
			||||||
 | 
					        ) -> u32;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        #[allow(improper_ctypes)]
 | 
				
			||||||
 | 
					        pub fn eek_render_button_in_context(
 | 
				
			||||||
 | 
					            scale_factor: u32,
 | 
				
			||||||
            cr: *mut cairo_sys::cairo_t,
 | 
					            cr: *mut cairo_sys::cairo_t,
 | 
				
			||||||
            button: *const Button,
 | 
					            ctx: GtkStyleContext,
 | 
				
			||||||
 | 
					            bounds: Bounds,
 | 
				
			||||||
 | 
					            icon_name: *const c_char,
 | 
				
			||||||
 | 
					            label: *const c_char,
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        #[allow(improper_ctypes)]
 | 
				
			||||||
 | 
					        pub fn eek_get_style_context_for_button(
 | 
				
			||||||
 | 
					            renderer: EekRenderer,
 | 
				
			||||||
 | 
					            name: *const c_char,
 | 
				
			||||||
 | 
					            outline_name: *const c_char,
 | 
				
			||||||
 | 
					            locked_class: *const c_char,
 | 
				
			||||||
            pressed: u64,
 | 
					            pressed: u64,
 | 
				
			||||||
            locked: u64,
 | 
					        ) -> GtkStyleContext;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        #[allow(improper_ctypes)]
 | 
				
			||||||
 | 
					        pub fn eek_put_style_context_for_button(
 | 
				
			||||||
 | 
					            ctx: GtkStyleContext,
 | 
				
			||||||
 | 
					            outline_name: *const c_char,
 | 
				
			||||||
 | 
					            locked_class: *const c_char,
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -54,13 +85,16 @@ mod c {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        layout.foreach_visible_button(|offset, button| {
 | 
					        layout.foreach_visible_button(|offset, button| {
 | 
				
			||||||
            let state = RefCell::borrow(&button.state).clone();
 | 
					            let state = RefCell::borrow(&button.state).clone();
 | 
				
			||||||
            let active_mod = match &state.action {
 | 
					
 | 
				
			||||||
                Action::ApplyModifier(m) => active_modifiers.contains(m),
 | 
					            let locked = LockedStyle::from_action(
 | 
				
			||||||
                _ => false,
 | 
					                &state.action,
 | 
				
			||||||
            };
 | 
					                &active_modifiers,
 | 
				
			||||||
            let locked = state.action.is_active(&layout.current_view)
 | 
					                layout.get_view_latched(),
 | 
				
			||||||
                | active_mod;
 | 
					                &layout.current_view,
 | 
				
			||||||
            if state.pressed == keyboard::PressType::Pressed || locked {
 | 
					            );
 | 
				
			||||||
 | 
					            if state.pressed == keyboard::PressType::Pressed
 | 
				
			||||||
 | 
					                || locked != LockedStyle::Free
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
                render_button_at_position(
 | 
					                render_button_at_position(
 | 
				
			||||||
                    renderer, &cr,
 | 
					                    renderer, &cr,
 | 
				
			||||||
                    offset,
 | 
					                    offset,
 | 
				
			||||||
@ -86,20 +120,55 @@ mod c {
 | 
				
			|||||||
                renderer, &cr,
 | 
					                renderer, &cr,
 | 
				
			||||||
                offset,
 | 
					                offset,
 | 
				
			||||||
                button.as_ref(),
 | 
					                button.as_ref(),
 | 
				
			||||||
                keyboard::PressType::Released, false,
 | 
					                keyboard::PressType::Released,
 | 
				
			||||||
 | 
					                LockedStyle::Free,
 | 
				
			||||||
            );
 | 
					            );
 | 
				
			||||||
        })
 | 
					        })
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Clone, Copy, PartialEq, Debug)]
 | 
				
			||||||
 | 
					enum LockedStyle {
 | 
				
			||||||
 | 
					    Free,
 | 
				
			||||||
 | 
					    Latched,
 | 
				
			||||||
 | 
					    Locked,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl LockedStyle {
 | 
				
			||||||
 | 
					    fn from_action(
 | 
				
			||||||
 | 
					        action: &Action,
 | 
				
			||||||
 | 
					        mods: &HashSet<Modifier>,
 | 
				
			||||||
 | 
					        latched_view: &LatchedState,
 | 
				
			||||||
 | 
					        current_view: &str,
 | 
				
			||||||
 | 
					    ) -> LockedStyle {
 | 
				
			||||||
 | 
					        let active_mod = match action {
 | 
				
			||||||
 | 
					            Action::ApplyModifier(m) => mods.contains(m),
 | 
				
			||||||
 | 
					            _ => false,
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        let active_view = action.is_active(current_view);
 | 
				
			||||||
 | 
					        let latched_button = match latched_view {
 | 
				
			||||||
 | 
					            LatchedState::Not => false,
 | 
				
			||||||
 | 
					            LatchedState::FromView(view) => !action.has_locked_appearance_from(view),
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        match (active_mod, active_view, latched_button) {
 | 
				
			||||||
 | 
					            // Modifiers don't latch.
 | 
				
			||||||
 | 
					            (true, _, _) => LockedStyle::Locked,
 | 
				
			||||||
 | 
					            (false, true, false) => LockedStyle::Locked,
 | 
				
			||||||
 | 
					            (false, true, true) => LockedStyle::Latched,
 | 
				
			||||||
 | 
					            _ => LockedStyle::Free,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Renders a button at a position (button's own bounds ignored)
 | 
					/// Renders a button at a position (button's own bounds ignored)
 | 
				
			||||||
pub fn render_button_at_position(
 | 
					fn render_button_at_position(
 | 
				
			||||||
    renderer: c::EekRenderer,
 | 
					    renderer: c::EekRenderer,
 | 
				
			||||||
    cr: &cairo::Context,
 | 
					    cr: &cairo::Context,
 | 
				
			||||||
    position: Point,
 | 
					    position: Point,
 | 
				
			||||||
    button: &Button,
 | 
					    button: &Button,
 | 
				
			||||||
    pressed: keyboard::PressType,
 | 
					    pressed: keyboard::PressType,
 | 
				
			||||||
    locked: bool,
 | 
					    locked: LockedStyle,
 | 
				
			||||||
) {
 | 
					) {
 | 
				
			||||||
    cr.save();
 | 
					    cr.save();
 | 
				
			||||||
    cr.translate(position.x, position.y);
 | 
					    cr.translate(position.x, position.y);
 | 
				
			||||||
@ -108,19 +177,110 @@ pub fn render_button_at_position(
 | 
				
			|||||||
        button.size.width, button.size.height
 | 
					        button.size.width, button.size.height
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
    cr.clip();
 | 
					    cr.clip();
 | 
				
			||||||
    unsafe {
 | 
					
 | 
				
			||||||
        c::eek_render_button(
 | 
					    let scale_factor = unsafe {
 | 
				
			||||||
 | 
					        c::eek_renderer_get_scale_factor(renderer)
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    let bounds = button.get_bounds();
 | 
				
			||||||
 | 
					    let (label_c, icon_name_c) = match &button.label {
 | 
				
			||||||
 | 
					        Label::Text(text) => (text.as_ptr(), ptr::null()),
 | 
				
			||||||
 | 
					        Label::IconName(name) => {
 | 
				
			||||||
 | 
					            let l = unsafe {
 | 
				
			||||||
 | 
					                // CStr doesn't allocate anything, so it only points to
 | 
				
			||||||
 | 
					                // the 'static str, avoiding a memory leak
 | 
				
			||||||
 | 
					                CStr::from_bytes_with_nul_unchecked(b"icon\0")
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					            (l.as_ptr(), name.as_ptr())
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    with_button_context(
 | 
				
			||||||
        renderer,
 | 
					        renderer,
 | 
				
			||||||
 | 
					        button,
 | 
				
			||||||
 | 
					        pressed,
 | 
				
			||||||
 | 
					        locked,
 | 
				
			||||||
 | 
					        |ctx| unsafe {
 | 
				
			||||||
 | 
					            // TODO: split into separate procedures:
 | 
				
			||||||
 | 
					            // draw outline, draw label, draw icon.
 | 
				
			||||||
 | 
					            c::eek_render_button_in_context(
 | 
				
			||||||
 | 
					                scale_factor,
 | 
				
			||||||
                cairo::Context::to_raw_none(&cr),
 | 
					                cairo::Context::to_raw_none(&cr),
 | 
				
			||||||
            button as *const Button,
 | 
					                *ctx,
 | 
				
			||||||
 | 
					                bounds,
 | 
				
			||||||
 | 
					                icon_name_c,
 | 
				
			||||||
 | 
					                label_c,
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    cr.restore();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn with_button_context<R, F: FnOnce(&c::GtkStyleContext) -> R>(
 | 
				
			||||||
 | 
					    renderer: c::EekRenderer,
 | 
				
			||||||
 | 
					    button: &Button,
 | 
				
			||||||
 | 
					    pressed: keyboard::PressType,
 | 
				
			||||||
 | 
					    locked: LockedStyle,
 | 
				
			||||||
 | 
					    operation: F,
 | 
				
			||||||
 | 
					) -> R {
 | 
				
			||||||
 | 
					    let outline_name_c = button.outline_name.as_ptr();
 | 
				
			||||||
 | 
					    let locked_class_c = match locked {
 | 
				
			||||||
 | 
					        LockedStyle::Free => ptr::null(),
 | 
				
			||||||
 | 
					        LockedStyle::Locked => unsafe {
 | 
				
			||||||
 | 
					            CStr::from_bytes_with_nul_unchecked(b"locked\0").as_ptr()
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        LockedStyle::Latched => unsafe {
 | 
				
			||||||
 | 
					            CStr::from_bytes_with_nul_unchecked(b"latched\0").as_ptr()
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    let ctx = unsafe {
 | 
				
			||||||
 | 
					        c::eek_get_style_context_for_button(
 | 
				
			||||||
 | 
					            renderer,
 | 
				
			||||||
 | 
					            button.name.as_ptr(),
 | 
				
			||||||
 | 
					            outline_name_c,
 | 
				
			||||||
 | 
					            locked_class_c,
 | 
				
			||||||
            pressed as u64,
 | 
					            pressed as u64,
 | 
				
			||||||
            locked as u64,
 | 
					 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
    cr.restore();
 | 
					    
 | 
				
			||||||
 | 
					    let r = operation(&ctx);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    unsafe {
 | 
				
			||||||
 | 
					        c::eek_put_style_context_for_button(
 | 
				
			||||||
 | 
					            ctx,
 | 
				
			||||||
 | 
					            outline_name_c,
 | 
				
			||||||
 | 
					            locked_class_c,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    r
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub fn queue_redraw(keyboard: EekGtkKeyboard) {
 | 
					pub fn queue_redraw(keyboard: EekGtkKeyboard) {
 | 
				
			||||||
    let widget = unsafe { gtk::Widget::from_glib_none(keyboard.0) };
 | 
					    let widget = unsafe { gtk::Widget::from_glib_none(keyboard.0) };
 | 
				
			||||||
    widget.queue_draw();
 | 
					    widget.queue_draw();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[cfg(test)]
 | 
				
			||||||
 | 
					mod test {
 | 
				
			||||||
 | 
					    use super::*;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[test]
 | 
				
			||||||
 | 
					    fn test_exit_only() {
 | 
				
			||||||
 | 
					        assert_eq!(
 | 
				
			||||||
 | 
					            LockedStyle::from_action(
 | 
				
			||||||
 | 
					                &Action::LockView {
 | 
				
			||||||
 | 
					                    lock: "ab".into(), 
 | 
				
			||||||
 | 
					                    unlock: "a".into(),
 | 
				
			||||||
 | 
					                    latches: true,
 | 
				
			||||||
 | 
					                    looks_locked_from: vec!["b".into()],
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                &HashSet::new(),
 | 
				
			||||||
 | 
					                &LatchedState::FromView("b".into()),
 | 
				
			||||||
 | 
					                "ab",
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            LockedStyle::Locked,
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -26,13 +26,6 @@ struct squeek_layout_state {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
struct squeek_layout;
 | 
					struct squeek_layout;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
EekBounds squeek_button_get_bounds(const struct squeek_button*);
 | 
					 | 
				
			||||||
const char *squeek_button_get_label(const struct squeek_button*);
 | 
					 | 
				
			||||||
const char *squeek_button_get_icon_name(const struct squeek_button*);
 | 
					 | 
				
			||||||
const char *squeek_button_get_name(const struct squeek_button*);
 | 
					 | 
				
			||||||
const char *squeek_button_get_outline_name(const struct squeek_button*);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void squeek_button_print(const struct squeek_button* button);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
struct transformation squeek_layout_calculate_transformation(
 | 
					struct transformation squeek_layout_calculate_transformation(
 | 
				
			||||||
        const struct squeek_layout *layout,
 | 
					        const struct squeek_layout *layout,
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										540
									
								
								src/layout.rs
									
									
									
									
									
								
							
							
						
						
									
										540
									
								
								src/layout.rs
									
									
									
									
									
								
							@ -41,9 +41,7 @@ pub mod c {
 | 
				
			|||||||
    use super::*;
 | 
					    use super::*;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    use gtk_sys;
 | 
					    use gtk_sys;
 | 
				
			||||||
    use std::ffi::CStr;
 | 
					    use std::os::raw::c_void;
 | 
				
			||||||
    use std::os::raw::{ c_char, c_void };
 | 
					 | 
				
			||||||
    use std::ptr;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    use std::ops::{ Add, Sub };
 | 
					    use std::ops::{ Add, Sub };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -162,64 +160,6 @@ pub mod c {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    // The following defined in Rust. TODO: wrap naked pointers to Rust data inside RefCells to prevent multiple writers
 | 
					    // The following defined in Rust. TODO: wrap naked pointers to Rust data inside RefCells to prevent multiple writers
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    #[no_mangle]
 | 
					 | 
				
			||||||
    pub extern "C"
 | 
					 | 
				
			||||||
    fn squeek_button_get_bounds(button: *const ::layout::Button) -> Bounds {
 | 
					 | 
				
			||||||
        let button = unsafe { &*button };
 | 
					 | 
				
			||||||
        Bounds {
 | 
					 | 
				
			||||||
            x: 0.0, y: 0.0,
 | 
					 | 
				
			||||||
            width: button.size.width, height: button.size.height
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    #[no_mangle]
 | 
					 | 
				
			||||||
    pub extern "C"
 | 
					 | 
				
			||||||
    fn squeek_button_get_label(
 | 
					 | 
				
			||||||
        button: *const ::layout::Button
 | 
					 | 
				
			||||||
    ) -> *const c_char {
 | 
					 | 
				
			||||||
        let button = unsafe { &*button };
 | 
					 | 
				
			||||||
        match &button.label {
 | 
					 | 
				
			||||||
            Label::Text(text) => text.as_ptr(),
 | 
					 | 
				
			||||||
            // returning static strings to C is a bit cumbersome
 | 
					 | 
				
			||||||
            Label::IconName(_) => unsafe {
 | 
					 | 
				
			||||||
                // CStr doesn't allocate anything, so it only points to
 | 
					 | 
				
			||||||
                // the 'static str, avoiding a memory leak
 | 
					 | 
				
			||||||
                CStr::from_bytes_with_nul_unchecked(b"icon\0")
 | 
					 | 
				
			||||||
            }.as_ptr(),
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    #[no_mangle]
 | 
					 | 
				
			||||||
    pub extern "C"
 | 
					 | 
				
			||||||
    fn squeek_button_get_icon_name(button: *const Button) -> *const c_char {
 | 
					 | 
				
			||||||
        let button = unsafe { &*button };
 | 
					 | 
				
			||||||
        match &button.label {
 | 
					 | 
				
			||||||
            Label::Text(_) => ptr::null(),
 | 
					 | 
				
			||||||
            Label::IconName(name) => name.as_ptr(),
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    #[no_mangle]
 | 
					 | 
				
			||||||
    pub extern "C"
 | 
					 | 
				
			||||||
    fn squeek_button_get_name(button: *const Button) -> *const c_char {
 | 
					 | 
				
			||||||
        let button = unsafe { &*button };
 | 
					 | 
				
			||||||
        button.name.as_ptr()
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    #[no_mangle]
 | 
					 | 
				
			||||||
    pub extern "C"
 | 
					 | 
				
			||||||
    fn squeek_button_get_outline_name(button: *const Button) -> *const c_char {
 | 
					 | 
				
			||||||
        let button = unsafe { &*button };
 | 
					 | 
				
			||||||
        button.outline_name.as_ptr()
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    #[no_mangle]
 | 
					 | 
				
			||||||
    pub extern "C"
 | 
					 | 
				
			||||||
    fn squeek_button_print(button: *const ::layout::Button) {
 | 
					 | 
				
			||||||
        let button = unsafe { &*button };
 | 
					 | 
				
			||||||
        println!("{:?}", button);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    /// Positions the layout contents within the available space.
 | 
					    /// Positions the layout contents within the available space.
 | 
				
			||||||
    /// The origin of the transformation is the point inside the margins.
 | 
					    /// The origin of the transformation is the point inside the margins.
 | 
				
			||||||
    #[no_mangle]
 | 
					    #[no_mangle]
 | 
				
			||||||
@ -484,6 +424,15 @@ pub struct Button {
 | 
				
			|||||||
    pub state: Rc<RefCell<KeyState>>,
 | 
					    pub state: Rc<RefCell<KeyState>>,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl Button {
 | 
				
			||||||
 | 
					    pub fn get_bounds(&self) -> c::Bounds {
 | 
				
			||||||
 | 
					        c::Bounds {
 | 
				
			||||||
 | 
					            x: 0.0, y: 0.0,
 | 
				
			||||||
 | 
					            width: self.size.width, height: self.size.height,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// The graphical representation of a row of buttons
 | 
					/// The graphical representation of a row of buttons
 | 
				
			||||||
#[derive(Clone, Debug)]
 | 
					#[derive(Clone, Debug)]
 | 
				
			||||||
pub struct Row {
 | 
					pub struct Row {
 | 
				
			||||||
@ -551,6 +500,7 @@ pub struct Spacing {
 | 
				
			|||||||
    pub button: f64,
 | 
					    pub button: f64,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Clone)]
 | 
				
			||||||
pub struct View {
 | 
					pub struct View {
 | 
				
			||||||
    /// Rows together with their offsets from the top left
 | 
					    /// Rows together with their offsets from the top left
 | 
				
			||||||
    rows: Vec<(c::Point, Row)>,
 | 
					    rows: Vec<(c::Point, Row)>,
 | 
				
			||||||
@ -664,6 +614,19 @@ pub struct Margins {
 | 
				
			|||||||
    pub right: f64,
 | 
					    pub right: f64,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Clone, Debug, PartialEq)]
 | 
				
			||||||
 | 
					pub enum LatchedState {
 | 
				
			||||||
 | 
					    /// Holds view to return to.
 | 
				
			||||||
 | 
					    FromView(String),
 | 
				
			||||||
 | 
					    Not,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl LatchedState {
 | 
				
			||||||
 | 
					    pub fn is_latched(&self) -> bool {
 | 
				
			||||||
 | 
					        self != &LatchedState::Not
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// TODO: split into sth like
 | 
					// TODO: split into sth like
 | 
				
			||||||
// Arrangement (views) + details (keymap) + State (keys)
 | 
					// Arrangement (views) + details (keymap) + State (keys)
 | 
				
			||||||
/// State of the UI, contains the backend as well
 | 
					/// State of the UI, contains the backend as well
 | 
				
			||||||
@ -671,6 +634,12 @@ pub struct Layout {
 | 
				
			|||||||
    pub margins: Margins,
 | 
					    pub margins: Margins,
 | 
				
			||||||
    pub kind: ArrangementKind,
 | 
					    pub kind: ArrangementKind,
 | 
				
			||||||
    pub current_view: String,
 | 
					    pub current_view: String,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // If current view is latched,
 | 
				
			||||||
 | 
					    // clicking any button that emits an action (erase, submit, set modifier)
 | 
				
			||||||
 | 
					    // will cause lock buttons to unlatch.
 | 
				
			||||||
 | 
					    view_latched: LatchedState,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Views own the actual buttons which have state
 | 
					    // Views own the actual buttons which have state
 | 
				
			||||||
    // Maybe they should own UI only,
 | 
					    // Maybe they should own UI only,
 | 
				
			||||||
    // and keys should be owned by a dedicated non-UI-State?
 | 
					    // and keys should be owned by a dedicated non-UI-State?
 | 
				
			||||||
@ -717,6 +686,7 @@ impl Layout {
 | 
				
			|||||||
        Layout {
 | 
					        Layout {
 | 
				
			||||||
            kind,
 | 
					            kind,
 | 
				
			||||||
            current_view: "base".to_owned(),
 | 
					            current_view: "base".to_owned(),
 | 
				
			||||||
 | 
					            view_latched: LatchedState::Not,
 | 
				
			||||||
            views: data.views,
 | 
					            views: data.views,
 | 
				
			||||||
            keymaps: data.keymaps,
 | 
					            keymaps: data.keymaps,
 | 
				
			||||||
            pressed_keys: HashSet::new(),
 | 
					            pressed_keys: HashSet::new(),
 | 
				
			||||||
@ -742,6 +712,12 @@ impl Layout {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Layout is passed around mutably,
 | 
				
			||||||
 | 
					    // so better keep the field away from direct access.
 | 
				
			||||||
 | 
					    pub fn get_view_latched(&self) -> &LatchedState {
 | 
				
			||||||
 | 
					        &self.view_latched
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// Calculates size without margins
 | 
					    /// Calculates size without margins
 | 
				
			||||||
    fn calculate_inner_size(&self) -> Size {
 | 
					    fn calculate_inner_size(&self) -> Size {
 | 
				
			||||||
        View::calculate_super_size(
 | 
					        View::calculate_super_size(
 | 
				
			||||||
@ -817,8 +793,117 @@ impl Layout {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
        out
 | 
					        out
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    fn apply_view_transition(
 | 
				
			||||||
 | 
					        &mut self,
 | 
				
			||||||
 | 
					        action: &Action,
 | 
				
			||||||
 | 
					    ) {
 | 
				
			||||||
 | 
					        let (transition, new_latched) = Layout::process_action_for_view(
 | 
				
			||||||
 | 
					            action,
 | 
				
			||||||
 | 
					            &self.current_view,
 | 
				
			||||||
 | 
					            &self.view_latched,
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        match transition {
 | 
				
			||||||
 | 
					            ViewTransition::UnlatchAll => self.unstick_locks(),
 | 
				
			||||||
 | 
					            ViewTransition::ChangeTo(view) => try_set_view(self, view.into()),
 | 
				
			||||||
 | 
					            ViewTransition::NoChange => {},
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.view_latched = new_latched;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// Unlatch all latched keys,
 | 
				
			||||||
 | 
					    /// so that the new view is the one before first press.
 | 
				
			||||||
 | 
					    fn unstick_locks(&mut self) {
 | 
				
			||||||
 | 
					        if let LatchedState::FromView(name) = self.view_latched.clone() {
 | 
				
			||||||
 | 
					            match self.set_view(name.clone()) {
 | 
				
			||||||
 | 
					                Ok(_) => { self.view_latched = LatchedState::Not; }
 | 
				
			||||||
 | 
					                Err(e) => log_print!(
 | 
				
			||||||
 | 
					                    logging::Level::Bug,
 | 
				
			||||||
 | 
					                    "Bad view {}, can't unlatch ({:?})",
 | 
				
			||||||
 | 
					                    name,
 | 
				
			||||||
 | 
					                    e,
 | 
				
			||||||
 | 
					                ),
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// Last bool is new latch state.
 | 
				
			||||||
 | 
					    /// It doesn't make sense when the result carries UnlatchAll,
 | 
				
			||||||
 | 
					    /// but let's not be picky.
 | 
				
			||||||
 | 
					    ///
 | 
				
			||||||
 | 
					    /// Although the state is not defined at the keys
 | 
				
			||||||
 | 
					    /// (it's in the relationship between view and action),
 | 
				
			||||||
 | 
					    /// keys go through the following stages when clicked repeatedly: 
 | 
				
			||||||
 | 
					    /// unlocked+unlatched -> locked+latched -> locked+unlatched
 | 
				
			||||||
 | 
					    /// -> unlocked+unlatched
 | 
				
			||||||
 | 
					    fn process_action_for_view<'a>(
 | 
				
			||||||
 | 
					        action: &'a Action,
 | 
				
			||||||
 | 
					        current_view: &str,
 | 
				
			||||||
 | 
					        latched: &LatchedState,
 | 
				
			||||||
 | 
					    ) -> (ViewTransition<'a>, LatchedState) {
 | 
				
			||||||
 | 
					        match action {
 | 
				
			||||||
 | 
					            Action::Submit { text: _, keys: _ }
 | 
				
			||||||
 | 
					                | Action::Erase
 | 
				
			||||||
 | 
					                | Action::ApplyModifier(_)
 | 
				
			||||||
 | 
					            => {
 | 
				
			||||||
 | 
					                let t = match latched {
 | 
				
			||||||
 | 
					                    LatchedState::FromView(_) => ViewTransition::UnlatchAll,
 | 
				
			||||||
 | 
					                    LatchedState::Not => ViewTransition::NoChange,
 | 
				
			||||||
 | 
					                };
 | 
				
			||||||
 | 
					                (t, LatchedState::Not)
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            Action::SetView(view) => (
 | 
				
			||||||
 | 
					                ViewTransition::ChangeTo(view),
 | 
				
			||||||
 | 
					                LatchedState::Not,
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            Action::LockView { lock, unlock, latches, looks_locked_from: _ } => {
 | 
				
			||||||
 | 
					                use self::ViewTransition as VT;
 | 
				
			||||||
 | 
					                let locked = action.is_locked(current_view);
 | 
				
			||||||
 | 
					                match (locked, latched, latches) {
 | 
				
			||||||
 | 
					                    // Was unlocked, now make locked but latched.
 | 
				
			||||||
 | 
					                    (false, LatchedState::Not, true) => (
 | 
				
			||||||
 | 
					                        VT::ChangeTo(lock),
 | 
				
			||||||
 | 
					                        LatchedState::FromView(current_view.into()),
 | 
				
			||||||
 | 
					                    ),
 | 
				
			||||||
 | 
					                    // Layout is latched for reason other than this button.
 | 
				
			||||||
 | 
					                    (false, LatchedState::FromView(view), true) => (
 | 
				
			||||||
 | 
					                        VT::ChangeTo(lock),
 | 
				
			||||||
 | 
					                        LatchedState::FromView(view.clone()),
 | 
				
			||||||
 | 
					                    ),
 | 
				
			||||||
 | 
					                    // Was latched, now only locked.
 | 
				
			||||||
 | 
					                    (true, LatchedState::FromView(_), true)
 | 
				
			||||||
 | 
					                        => (VT::NoChange, LatchedState::Not),
 | 
				
			||||||
 | 
					                    // Was unlocked, can't latch so now make fully locked.
 | 
				
			||||||
 | 
					                    (false, _, false)
 | 
				
			||||||
 | 
					                        => (VT::ChangeTo(lock), LatchedState::Not),
 | 
				
			||||||
 | 
					                    // Was locked, now make unlocked.
 | 
				
			||||||
 | 
					                    (true, _, _)
 | 
				
			||||||
 | 
					                        => (VT::ChangeTo(unlock), LatchedState::Not),
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            _ => (ViewTransition::NoChange, latched.clone()),
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Debug, PartialEq)]
 | 
				
			||||||
 | 
					enum ViewTransition<'a> {
 | 
				
			||||||
 | 
					    ChangeTo(&'a str),
 | 
				
			||||||
 | 
					    UnlatchAll,
 | 
				
			||||||
 | 
					    NoChange,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn try_set_view(layout: &mut Layout, view_name: &str) {
 | 
				
			||||||
 | 
					    layout.set_view(view_name.into())
 | 
				
			||||||
 | 
					        .or_print(
 | 
				
			||||||
 | 
					            logging::Problem::Bug,
 | 
				
			||||||
 | 
					            &format!("Bad view {}, ignoring", view_name),
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
mod procedures {
 | 
					mod procedures {
 | 
				
			||||||
    use super::*;
 | 
					    use super::*;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -897,67 +982,6 @@ pub struct UIBackend {
 | 
				
			|||||||
mod seat {
 | 
					mod seat {
 | 
				
			||||||
    use super::*;
 | 
					    use super::*;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fn try_set_view(layout: &mut Layout, view_name: String) {
 | 
					 | 
				
			||||||
        layout.set_view(view_name.clone())
 | 
					 | 
				
			||||||
            .or_print(
 | 
					 | 
				
			||||||
                logging::Problem::Bug,
 | 
					 | 
				
			||||||
                &format!("Bad view {}, ignoring", view_name),
 | 
					 | 
				
			||||||
            );
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /// A vessel holding an obligation to switch view.
 | 
					 | 
				
			||||||
    /// Use with #[must_use]
 | 
					 | 
				
			||||||
    struct ViewChange<'a> {
 | 
					 | 
				
			||||||
        layout: &'a mut Layout,
 | 
					 | 
				
			||||||
        view_name: Option<String>,
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    impl<'a> ViewChange<'a> {
 | 
					 | 
				
			||||||
        fn choose_view(self, view_name: String) -> ViewChange<'a> {
 | 
					 | 
				
			||||||
            ViewChange {
 | 
					 | 
				
			||||||
                view_name: Some(view_name),
 | 
					 | 
				
			||||||
                ..self
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        fn apply(self) {
 | 
					 | 
				
			||||||
            if let Some(name) = self.view_name {
 | 
					 | 
				
			||||||
                try_set_view(self.layout, name);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /// Find all impermanent view changes and undo them in an arbitrary order.
 | 
					 | 
				
			||||||
    /// Return an obligation to actually switch the view.
 | 
					 | 
				
			||||||
    /// The final view is the "unlock" view
 | 
					 | 
				
			||||||
    /// from one of the currently stuck keys.
 | 
					 | 
				
			||||||
    // As long as only one stuck button is used, this should be fine.
 | 
					 | 
				
			||||||
    // This is guaranteed because pressing a lock button unlocks all others.
 | 
					 | 
				
			||||||
    // TODO: Make some broader guarantee about the resulting view,
 | 
					 | 
				
			||||||
    // e.g. by maintaining a stack of stuck keys.
 | 
					 | 
				
			||||||
    #[must_use]
 | 
					 | 
				
			||||||
    fn unstick_locks(layout: &mut Layout) -> ViewChange {
 | 
					 | 
				
			||||||
        let mut new_view = None;
 | 
					 | 
				
			||||||
        for key in layout.get_locked_keys().clone() {
 | 
					 | 
				
			||||||
            let key: &Rc<RefCell<KeyState>> = key.borrow();
 | 
					 | 
				
			||||||
            let key = RefCell::borrow(key);
 | 
					 | 
				
			||||||
            match &key.action {
 | 
					 | 
				
			||||||
                Action::LockView { lock: _, unlock: view } => {
 | 
					 | 
				
			||||||
                    new_view = Some(view.clone());
 | 
					 | 
				
			||||||
                },
 | 
					 | 
				
			||||||
                a => log_print!(
 | 
					 | 
				
			||||||
                    logging::Level::Bug,
 | 
					 | 
				
			||||||
                    "Non-locking action {:?} was found inside locked keys",
 | 
					 | 
				
			||||||
                    a,
 | 
					 | 
				
			||||||
                ),
 | 
					 | 
				
			||||||
            };
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        ViewChange {
 | 
					 | 
				
			||||||
            layout,
 | 
					 | 
				
			||||||
            view_name: new_view,
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    pub fn handle_press_key(
 | 
					    pub fn handle_press_key(
 | 
				
			||||||
        layout: &mut Layout,
 | 
					        layout: &mut Layout,
 | 
				
			||||||
        submission: &mut Submission,
 | 
					        submission: &mut Submission,
 | 
				
			||||||
@ -1017,37 +1041,22 @@ mod seat {
 | 
				
			|||||||
        };
 | 
					        };
 | 
				
			||||||
        let action = key.action.clone();
 | 
					        let action = key.action.clone();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        layout.apply_view_transition(&action);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // update
 | 
					        // update
 | 
				
			||||||
        let key = key.into_released();
 | 
					        let key = key.into_released();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // process changes
 | 
					        // process non-view switching
 | 
				
			||||||
        match action {
 | 
					        match action {
 | 
				
			||||||
            Action::Submit { text: _, keys: _ }
 | 
					            Action::Submit { text: _, keys: _ }
 | 
				
			||||||
                | Action::Erase
 | 
					                | Action::Erase
 | 
				
			||||||
            => {
 | 
					            => {
 | 
				
			||||||
                unstick_locks(layout).apply();
 | 
					 | 
				
			||||||
                submission.handle_release(KeyState::get_id(rckey), time);
 | 
					                submission.handle_release(KeyState::get_id(rckey), time);
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
            Action::SetView(view) => {
 | 
					 | 
				
			||||||
                try_set_view(layout, view)
 | 
					 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
            Action::LockView { lock, unlock } => {
 | 
					 | 
				
			||||||
                let gets_locked = !key.action.is_locked(&layout.current_view);
 | 
					 | 
				
			||||||
                unstick_locks(layout)
 | 
					 | 
				
			||||||
                    // It doesn't matter what the resulting view should be,
 | 
					 | 
				
			||||||
                    // it's getting changed anyway.
 | 
					 | 
				
			||||||
                    .choose_view(
 | 
					 | 
				
			||||||
                        match gets_locked {
 | 
					 | 
				
			||||||
                            true => lock.clone(),
 | 
					 | 
				
			||||||
                            false => unlock.clone(),
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
                    )
 | 
					 | 
				
			||||||
                    .apply()
 | 
					 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
            Action::ApplyModifier(modifier) => {
 | 
					            Action::ApplyModifier(modifier) => {
 | 
				
			||||||
                // FIXME: key id is unneeded with stateless locks
 | 
					                // FIXME: key id is unneeded with stateless locks
 | 
				
			||||||
                let key_id = KeyState::get_id(rckey);
 | 
					                let key_id = KeyState::get_id(rckey);
 | 
				
			||||||
                let gets_locked = !submission.is_modifier_active(modifier.clone());
 | 
					                let gets_locked = !submission.is_modifier_active(modifier);
 | 
				
			||||||
                match gets_locked {
 | 
					                match gets_locked {
 | 
				
			||||||
                    true => submission.handle_add_modifier(
 | 
					                    true => submission.handle_add_modifier(
 | 
				
			||||||
                        key_id,
 | 
					                        key_id,
 | 
				
			||||||
@ -1082,6 +1091,8 @@ mod seat {
 | 
				
			|||||||
                    }
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
 | 
					            // Other keys are handled in view switcher before.
 | 
				
			||||||
 | 
					            _ => {}
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let pointer = ::util::Pointer(rckey.clone());
 | 
					        let pointer = ::util::Pointer(rckey.clone());
 | 
				
			||||||
@ -1099,14 +1110,20 @@ mod test {
 | 
				
			|||||||
    use std::ffi::CString;
 | 
					    use std::ffi::CString;
 | 
				
			||||||
    use ::keyboard::PressType;
 | 
					    use ::keyboard::PressType;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pub fn make_state() -> Rc<RefCell<::keyboard::KeyState>> {
 | 
					    pub fn make_state_with_action(action: Action)
 | 
				
			||||||
 | 
					        -> Rc<RefCell<::keyboard::KeyState>>
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
        Rc::new(RefCell::new(::keyboard::KeyState {
 | 
					        Rc::new(RefCell::new(::keyboard::KeyState {
 | 
				
			||||||
            pressed: PressType::Released,
 | 
					            pressed: PressType::Released,
 | 
				
			||||||
            keycodes: Vec::new(),
 | 
					            keycodes: Vec::new(),
 | 
				
			||||||
            action: Action::SetView("default".into()),
 | 
					            action,
 | 
				
			||||||
        }))
 | 
					        }))
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn make_state() -> Rc<RefCell<::keyboard::KeyState>> {
 | 
				
			||||||
 | 
					        make_state_with_action(Action::SetView("default".into()))
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pub fn make_button_with_state(
 | 
					    pub fn make_button_with_state(
 | 
				
			||||||
        name: String,
 | 
					        name: String,
 | 
				
			||||||
        state: Rc<RefCell<::keyboard::KeyState>>,
 | 
					        state: Rc<RefCell<::keyboard::KeyState>>,
 | 
				
			||||||
@ -1120,6 +1137,242 @@ mod test {
 | 
				
			|||||||
        })
 | 
					        })
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
 | 
					    #[test]
 | 
				
			||||||
 | 
					    fn latch_lock_unlock() {
 | 
				
			||||||
 | 
					        let action = Action::LockView {
 | 
				
			||||||
 | 
					            lock: "lock".into(),
 | 
				
			||||||
 | 
					            unlock: "unlock".into(),
 | 
				
			||||||
 | 
					            latches: true,
 | 
				
			||||||
 | 
					            looks_locked_from: vec![],
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        assert_eq!(
 | 
				
			||||||
 | 
					            Layout::process_action_for_view(&action, "unlock", &LatchedState::Not),
 | 
				
			||||||
 | 
					            (ViewTransition::ChangeTo("lock"), LatchedState::FromView("unlock".into())),
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        assert_eq!(
 | 
				
			||||||
 | 
					            Layout::process_action_for_view(&action, "lock", &LatchedState::FromView("unlock".into())),
 | 
				
			||||||
 | 
					            (ViewTransition::NoChange, LatchedState::Not),
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        assert_eq!(
 | 
				
			||||||
 | 
					            Layout::process_action_for_view(&action, "lock", &LatchedState::Not),
 | 
				
			||||||
 | 
					            (ViewTransition::ChangeTo("unlock"), LatchedState::Not),
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        assert_eq!(
 | 
				
			||||||
 | 
					            Layout::process_action_for_view(&Action::Erase, "lock", &LatchedState::FromView("base".into())),
 | 
				
			||||||
 | 
					            (ViewTransition::UnlatchAll, LatchedState::Not),
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[test]
 | 
				
			||||||
 | 
					    fn latch_pop_layout() {
 | 
				
			||||||
 | 
					        let switch = Action::LockView {
 | 
				
			||||||
 | 
					            lock: "locked".into(),
 | 
				
			||||||
 | 
					            unlock: "base".into(),
 | 
				
			||||||
 | 
					            latches: true,
 | 
				
			||||||
 | 
					            looks_locked_from: vec![],
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        let submit = Action::Erase;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let view = View::new(vec![(
 | 
				
			||||||
 | 
					            0.0,
 | 
				
			||||||
 | 
					            Row::new(vec![
 | 
				
			||||||
 | 
					                (
 | 
				
			||||||
 | 
					                    0.0,
 | 
				
			||||||
 | 
					                    make_button_with_state(
 | 
				
			||||||
 | 
					                        "switch".into(),
 | 
				
			||||||
 | 
					                        make_state_with_action(switch.clone())
 | 
				
			||||||
 | 
					                    ),
 | 
				
			||||||
 | 
					                ),
 | 
				
			||||||
 | 
					                (
 | 
				
			||||||
 | 
					                    1.0,
 | 
				
			||||||
 | 
					                    make_button_with_state(
 | 
				
			||||||
 | 
					                        "submit".into(),
 | 
				
			||||||
 | 
					                        make_state_with_action(submit.clone())
 | 
				
			||||||
 | 
					                    ),
 | 
				
			||||||
 | 
					                ),
 | 
				
			||||||
 | 
					            ]),
 | 
				
			||||||
 | 
					        )]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let mut layout = Layout {
 | 
				
			||||||
 | 
					            current_view: "base".into(),
 | 
				
			||||||
 | 
					            view_latched: LatchedState::Not,
 | 
				
			||||||
 | 
					            keymaps: Vec::new(),
 | 
				
			||||||
 | 
					            kind: ArrangementKind::Base,
 | 
				
			||||||
 | 
					            pressed_keys: HashSet::new(),
 | 
				
			||||||
 | 
					            margins: Margins {
 | 
				
			||||||
 | 
					                top: 0.0,
 | 
				
			||||||
 | 
					                left: 0.0,
 | 
				
			||||||
 | 
					                right: 0.0,
 | 
				
			||||||
 | 
					                bottom: 0.0,
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            views: hashmap! {
 | 
				
			||||||
 | 
					                // Both can use the same structure.
 | 
				
			||||||
 | 
					                // Switching doesn't depend on the view shape
 | 
				
			||||||
 | 
					                // as long as the switching button is present.
 | 
				
			||||||
 | 
					                "base".into() => (c::Point { x: 0.0, y: 0.0 }, view.clone()),
 | 
				
			||||||
 | 
					                "locked".into() => (c::Point { x: 0.0, y: 0.0 }, view),
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        };        
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Basic cycle
 | 
				
			||||||
 | 
					        layout.apply_view_transition(&switch);
 | 
				
			||||||
 | 
					        assert_eq!(&layout.current_view, "locked");
 | 
				
			||||||
 | 
					        layout.apply_view_transition(&switch);
 | 
				
			||||||
 | 
					        assert_eq!(&layout.current_view, "locked");
 | 
				
			||||||
 | 
					        layout.apply_view_transition(&submit);
 | 
				
			||||||
 | 
					        assert_eq!(&layout.current_view, "locked");
 | 
				
			||||||
 | 
					        layout.apply_view_transition(&switch);
 | 
				
			||||||
 | 
					        assert_eq!(&layout.current_view, "base");
 | 
				
			||||||
 | 
					        layout.apply_view_transition(&switch);
 | 
				
			||||||
 | 
					        // Unlatch
 | 
				
			||||||
 | 
					        assert_eq!(&layout.current_view, "locked");
 | 
				
			||||||
 | 
					        layout.apply_view_transition(&submit);
 | 
				
			||||||
 | 
					        assert_eq!(&layout.current_view, "base");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[test]
 | 
				
			||||||
 | 
					    fn reverse_unlatch_layout() {
 | 
				
			||||||
 | 
					        let switch = Action::LockView {
 | 
				
			||||||
 | 
					            lock: "locked".into(),
 | 
				
			||||||
 | 
					            unlock: "base".into(),
 | 
				
			||||||
 | 
					            latches: true,
 | 
				
			||||||
 | 
					            looks_locked_from: vec![],
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        let unswitch = Action::LockView {
 | 
				
			||||||
 | 
					            lock: "locked".into(),
 | 
				
			||||||
 | 
					            unlock: "unlocked".into(),
 | 
				
			||||||
 | 
					            latches: false,
 | 
				
			||||||
 | 
					            looks_locked_from: vec![],
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        let submit = Action::Erase;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let view = View::new(vec![(
 | 
				
			||||||
 | 
					            0.0,
 | 
				
			||||||
 | 
					            Row::new(vec![
 | 
				
			||||||
 | 
					                (
 | 
				
			||||||
 | 
					                    0.0,
 | 
				
			||||||
 | 
					                    make_button_with_state(
 | 
				
			||||||
 | 
					                        "switch".into(),
 | 
				
			||||||
 | 
					                        make_state_with_action(switch.clone())
 | 
				
			||||||
 | 
					                    ),
 | 
				
			||||||
 | 
					                ),
 | 
				
			||||||
 | 
					                (
 | 
				
			||||||
 | 
					                    1.0,
 | 
				
			||||||
 | 
					                    make_button_with_state(
 | 
				
			||||||
 | 
					                        "submit".into(),
 | 
				
			||||||
 | 
					                        make_state_with_action(submit.clone())
 | 
				
			||||||
 | 
					                    ),
 | 
				
			||||||
 | 
					                ),
 | 
				
			||||||
 | 
					            ]),
 | 
				
			||||||
 | 
					        )]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let mut layout = Layout {
 | 
				
			||||||
 | 
					            current_view: "base".into(),
 | 
				
			||||||
 | 
					            view_latched: LatchedState::Not,
 | 
				
			||||||
 | 
					            keymaps: Vec::new(),
 | 
				
			||||||
 | 
					            kind: ArrangementKind::Base,
 | 
				
			||||||
 | 
					            pressed_keys: HashSet::new(),
 | 
				
			||||||
 | 
					            margins: Margins {
 | 
				
			||||||
 | 
					                top: 0.0,
 | 
				
			||||||
 | 
					                left: 0.0,
 | 
				
			||||||
 | 
					                right: 0.0,
 | 
				
			||||||
 | 
					                bottom: 0.0,
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            views: hashmap! {
 | 
				
			||||||
 | 
					                // Both can use the same structure.
 | 
				
			||||||
 | 
					                // Switching doesn't depend on the view shape
 | 
				
			||||||
 | 
					                // as long as the switching button is present.
 | 
				
			||||||
 | 
					                "base".into() => (c::Point { x: 0.0, y: 0.0 }, view.clone()),
 | 
				
			||||||
 | 
					                "locked".into() => (c::Point { x: 0.0, y: 0.0 }, view.clone()),
 | 
				
			||||||
 | 
					                "unlocked".into() => (c::Point { x: 0.0, y: 0.0 }, view),
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        };        
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        layout.apply_view_transition(&switch);
 | 
				
			||||||
 | 
					        assert_eq!(&layout.current_view, "locked");
 | 
				
			||||||
 | 
					        layout.apply_view_transition(&unswitch);
 | 
				
			||||||
 | 
					        assert_eq!(&layout.current_view, "unlocked");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[test]
 | 
				
			||||||
 | 
					    fn latch_twopop_layout() {
 | 
				
			||||||
 | 
					        let switch = Action::LockView {
 | 
				
			||||||
 | 
					            lock: "locked".into(),
 | 
				
			||||||
 | 
					            unlock: "base".into(),
 | 
				
			||||||
 | 
					            latches: true,
 | 
				
			||||||
 | 
					            looks_locked_from: vec![],
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        let switch_again = Action::LockView {
 | 
				
			||||||
 | 
					            lock: "ĄĘ".into(),
 | 
				
			||||||
 | 
					            unlock: "locked".into(),
 | 
				
			||||||
 | 
					            latches: true,
 | 
				
			||||||
 | 
					            looks_locked_from: vec![],
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        let submit = Action::Erase;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let view = View::new(vec![(
 | 
				
			||||||
 | 
					            0.0,
 | 
				
			||||||
 | 
					            Row::new(vec![
 | 
				
			||||||
 | 
					                (
 | 
				
			||||||
 | 
					                    0.0,
 | 
				
			||||||
 | 
					                    make_button_with_state(
 | 
				
			||||||
 | 
					                        "switch".into(),
 | 
				
			||||||
 | 
					                        make_state_with_action(switch.clone())
 | 
				
			||||||
 | 
					                    ),
 | 
				
			||||||
 | 
					                ),
 | 
				
			||||||
 | 
					                (
 | 
				
			||||||
 | 
					                    1.0,
 | 
				
			||||||
 | 
					                    make_button_with_state(
 | 
				
			||||||
 | 
					                        "submit".into(),
 | 
				
			||||||
 | 
					                        make_state_with_action(submit.clone())
 | 
				
			||||||
 | 
					                    ),
 | 
				
			||||||
 | 
					                ),
 | 
				
			||||||
 | 
					            ]),
 | 
				
			||||||
 | 
					        )]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let mut layout = Layout {
 | 
				
			||||||
 | 
					            current_view: "base".into(),
 | 
				
			||||||
 | 
					            view_latched: LatchedState::Not,
 | 
				
			||||||
 | 
					            keymaps: Vec::new(),
 | 
				
			||||||
 | 
					            kind: ArrangementKind::Base,
 | 
				
			||||||
 | 
					            pressed_keys: HashSet::new(),
 | 
				
			||||||
 | 
					            margins: Margins {
 | 
				
			||||||
 | 
					                top: 0.0,
 | 
				
			||||||
 | 
					                left: 0.0,
 | 
				
			||||||
 | 
					                right: 0.0,
 | 
				
			||||||
 | 
					                bottom: 0.0,
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            views: hashmap! {
 | 
				
			||||||
 | 
					                // All can use the same structure.
 | 
				
			||||||
 | 
					                // Switching doesn't depend on the view shape
 | 
				
			||||||
 | 
					                // as long as the switching button is present.
 | 
				
			||||||
 | 
					                "base".into() => (c::Point { x: 0.0, y: 0.0 }, view.clone()),
 | 
				
			||||||
 | 
					                "locked".into() => (c::Point { x: 0.0, y: 0.0 }, view.clone()),
 | 
				
			||||||
 | 
					                "ĄĘ".into() => (c::Point { x: 0.0, y: 0.0 }, view),
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        };        
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Latch twice, then Ąto-unlatch across 2 levels
 | 
				
			||||||
 | 
					        layout.apply_view_transition(&switch);
 | 
				
			||||||
 | 
					        println!("{:?}", layout.view_latched);
 | 
				
			||||||
 | 
					        assert_eq!(&layout.current_view, "locked");
 | 
				
			||||||
 | 
					        layout.apply_view_transition(&switch_again);
 | 
				
			||||||
 | 
					        println!("{:?}", layout.view_latched);
 | 
				
			||||||
 | 
					        assert_eq!(&layout.current_view, "ĄĘ");
 | 
				
			||||||
 | 
					        layout.apply_view_transition(&submit);
 | 
				
			||||||
 | 
					        println!("{:?}", layout.view_latched);
 | 
				
			||||||
 | 
					        assert_eq!(&layout.current_view, "base");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    #[test]
 | 
					    #[test]
 | 
				
			||||||
    fn check_centering() {
 | 
					    fn check_centering() {
 | 
				
			||||||
        //    A B
 | 
					        //    A B
 | 
				
			||||||
@ -1192,6 +1445,7 @@ mod test {
 | 
				
			|||||||
        ]);
 | 
					        ]);
 | 
				
			||||||
        let layout = Layout {
 | 
					        let layout = Layout {
 | 
				
			||||||
            current_view: String::new(),
 | 
					            current_view: String::new(),
 | 
				
			||||||
 | 
					            view_latched: LatchedState::Not,
 | 
				
			||||||
            keymaps: Vec::new(),
 | 
					            keymaps: Vec::new(),
 | 
				
			||||||
            kind: ArrangementKind::Base,
 | 
					            kind: ArrangementKind::Base,
 | 
				
			||||||
            pressed_keys: HashSet::new(),
 | 
					            pressed_keys: HashSet::new(),
 | 
				
			||||||
 | 
				
			|||||||
@ -1,9 +0,0 @@
 | 
				
			|||||||
#ifndef POPOVER_H__
 | 
					 | 
				
			||||||
#define POPOVER_H__
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include <gtk/gtk.h>
 | 
					 | 
				
			||||||
#include "eek/eek-keyboard.h"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void squeek_popover_show(GtkWidget*, struct button_place);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
		Reference in New Issue
	
	Block a user