Making a clickable box in GTK4 - rust

How can I make a gtk4::Box clickable in gtk_rs?
In GTK3 it seems that using an EventBox was the way to achieve this, however in GTK4:
Stop using GtkEventBox
GtkEventBox is no longer needed and has been removed.
All widgets receive all events.
https://docs.gtk.org/gtk4/migrating-3to4.html#stop-using-gtkeventbox
So it seems that click handlers should now be attached to widgets directly. However I can't find any clear documentation or examples on how to do this.
If someone could give me an example of attaching a click listener to a gtk4::Box it would be much appreciated.

You can do this using gtk::GestureClick like shown in this example.
Here is a full example (click somewhere inside the window as the box is not actually visible):
use gtk::prelude::*;
use gtk::{Application, ApplicationWindow};
fn main() {
// Create a new application
let app = Application::builder()
.application_id("org.gtk-rs.example")
.build();
// Connect to "activate" signal of `app`
app.connect_activate(|app| {
build_ui(app);
});
// Run the application
app.run();
}
fn build_ui(app: &Application) {
// Create a window and set the title
let window = ApplicationWindow::builder()
.application(app)
.title("My GTK App")
.build();
// Create a box
let gtk_box = gtk::builders::BoxBuilder::new()
.height_request(200)
.width_request(300)
.build();
// Assign a click listener
let gesture = gtk::GestureClick::new();
gesture.connect_released(|gesture, _, _, _| {
gesture.set_state(gtk::EventSequenceState::Claimed);
println!("Box pressed!");
});
gtk_box.add_controller(&gesture);
// Add the box
window.set_child(Some(&gtk_box));
// Present window to the user
window.present();
}
A more involved example
Say you wanted to actually also see the box and maybe provide some user feedback on mouse hover. Then you will need to use CSS rules to achieve that.
Change your main.rs to:
use gtk::gdk::Display;
use gtk::{CssProvider, StyleContext, prelude::*};
use gtk::{Application, ApplicationWindow};
fn main() {
// Create a new application
let app = Application::builder()
.application_id("org.gtk-rs.example")
.build();
// Connect to "activate" signal of `app`
app.connect_activate(|app| {
// Load a CSS stylesheet from the included bytes.
let provider = CssProvider::new();
provider.load_from_data(include_bytes!("style.css"));
// Give the CssProvider to the default screen so the CSS rules are
// applied to the window.
StyleContext::add_provider_for_display(
&Display::default().expect("Error initializing gtk css provider."),
&provider,
gtk::STYLE_PROVIDER_PRIORITY_APPLICATION,
);
build_ui(app);
});
// Run the application
app.run();
}
fn build_ui(app: &Application) {
// Create a window and set the title
let window = ApplicationWindow::builder()
.application(app)
.title("My GTK App")
.build();
// Create a box
let gtk_box = gtk::builders::BoxBuilder::new()
.height_request(200)
.width_request(300)
.margin_bottom(20)
.margin_end(20)
.margin_start(20)
.margin_top(20)
.css_classes(vec![String::from("hover-box")])
.build();
// Assign a click listener
let gesture = gtk::GestureClick::new();
gesture.connect_released(|gesture, _, _, _| {
gesture.set_state(gtk::EventSequenceState::Claimed);
println!("Box pressed!");
});
gtk_box.add_controller(&gesture);
// Add the box
window.set_child(Some(&gtk_box));
// Present window to the user
window.present();
}
and create a style.css file in the src/ directory (the same directory where the main.rs is located) with this content:
.hover-box {
background-color: blue;
border-radius: 5px;
}
.hover-box:hover {
background-color: rgb(11, 133, 240);
border-radius: 5px;
}
Now you get a gtk4::Box with a 5px border radius and blue background color that changes when you hover the mouse above the box.

Related

React Hide Component when Mobile Keyboard open

I'm wondering if there is a way to hide React components (Ex. Bottom Navigation Bar Component) while the mobile (Andriod, iOS, etc. ) Keyboard is open on the screen.
Currently I'm doing it with a:
#media (max-height: 400px) {
.navclass {
display: none;
}
}
but am wondering if there is a JS Event or similar.
When am input element is focused on devices of inbuilt keyboards, the keyboards pops up.
ReactJS allows you to write Normal JavaScript in your code.so what you can do is to
this.state = {
isNavVisible: true
}
You can now pass this dynamically as a prop on the components and set it to the style of the wrappers element as an inline style in the components main file.
You can then write a function on your inputs or elements that takes focus:
const disableNav = () => {
if(windows.innerWidth <= 400){
this.setState({isNavVisible: false})
}
}

Phaser 3. Change dimensions of the game during runtime

Hi please help me to find out how trully responsive game can be created with Phaser3.
Respnsiveness is critical because game (representation layer of Blockly workspace) should be able to be exapanded to larger portion on screen and shrinked back many times during the session.
The question is How I can change dimentions of the game in runtime?
--- edited ---
It turns out there is pure css solution, canvas can be ajusted with css zoom property. In browser works well (no noticeable effect on performance), in cordova app (android) works too.
Here is Richard Davey's answer if css zoom can break things:
I've never actually tried it to be honest. Give it a go and see what
happens! It might break input, or it may carry on working. That (and
possibly the Scale Manager) are the only things it would break,
though, nothing else is likely to care much.
// is size of html element size that needed to fit
let props = { w: 1195, h: 612, elementId: 'myGame' };
// need to know game fixed size
let gameW = 1000, gameH = 750;
// detect zoom ratio from width or height
let isScaleWidth = gameW / gameH > props.w / props.h ? true : false;
// find zoom ratio
let zoom = isScaleWidth ? props.w / gameW : props.h / gameH;
// get DOM element, props.elementId is parent prop from Phaser game config
let el = document.getElementById(props.elementId);
// set css zoom of canvas element
el.style.zoom = zoom;
Resize the renderer as you're doing, but you also need to update the world bounds, as well as possibly the camera's bounds.
// the x,y, width and height of the games world
// the true, true, true, true, is setting which side of the world bounding box
// to check for collisions
this.physics.world.setBounds(x, y, width, height, true, true, true, true);
// setting the camera bound will set where the camera can scroll to
// I use the 'main' camera here fro simplicity, use whichever camera you use
this.cameras.main.setBounds(0, 0, width, height);
and that's how you can set the world boundary dynamically.
window.addEventListener('resize', () => {
game.resize(window.innerWidth, window.innerHeight);
});
There is some builtin support for resizing that can be configured in the Game Config. Check out the ScaleManager options. You have a number of options you can specify in the scale property, based on your needs.
I ended up using the following:
var config = {
type: Phaser.AUTO,
parent: 'game',
width: 1280, // initial width that determines the scaled size
height: 690,
scale: {
mode: Phaser.Scale.WIDTH_CONTROLS_HEIGHT ,
autoCenter: Phaser.Scale.CENTER_BOTH
},
physics: {
default: 'arcade',
arcade: {
gravity: {y: 0, x: 0},
debug: true
}
},
scene: {
key: 'main',
preload: preload,
create: this.create,
update: this.update
},
banner: false,
};
Just in case anybody else still has this problem, I found that just resizing the game didn't work for me and physics didn't do anything in my case.
To get it to work I needed to resize the game and also the scene's viewport (you can get the scene via the scenemanager which is a property of the game):
game.resize(width,height)
scene.cameras.main.setViewport(0,0,width,height)
For Phaser 3 the resize of the game now live inside the scale like this:
window.addEventListener('resize', () => {
game.scale.resize(window.innerWidth, window.innerHeight);
});
But if you need the entire game scale up and down only need this config:
const gameConfig: Phaser.Types.Core.GameConfig = {
...
scale: {
mode: Phaser.Scale.WIDTH_CONTROLS_HEIGHT,
},
...
};
export const game = new Phaser.Game(gameConfig);
Take a look at this article.
It explains how to dynamically resize the canvas while maintaining game ratio.
Note: All the code below is from the link above. I did not right any of this, it is sourced from the article linked above, but I am posting it here in case the link breaks in the future.
It uses CSS to center the canvas:
canvas{
display:block;
margin: 0;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
It listens to the window 'resize' event and calls a function to resize the game.
Listening to the event:
window.onload = function(){
var gameConfig = {
//config here
};
var game = new Phaser.Game(gameConfig);
resize();
window.addEventListener("resize", resize, false);
}
Resizing the game:
function resize() {
var canvas = document.querySelector("canvas");
var windowWidth = window.innerWidth;
var windowHeight = window.innerHeight;
var windowRatio = windowWidth / windowHeight;
var gameRatio = game.config.width / game.config.height;
if(windowRatio < gameRatio){
canvas.style.width = windowWidth + "px";
canvas.style.height = (windowWidth / gameRatio) + "px";
}
else{
canvas.style.width = (windowHeight * gameRatio) + "px";
canvas.style.height = windowHeight + "px";
}
}
I have used and modified this code in my phaser 3 project and it works great.
If you want to see other ways to resize dynamically check here
Put this in your create function:
const resize = ()=>{
game.scale.resize(window.innerWidth, window.innerHeight)
}
window.addEventListener("resize", resize, false);
Important! Make sure you have phaser 3.16+
Or else this won't work!!!
Use the game.resize function:
// when the page is loaded, create our game instance
window.onload = () => {
var game = new Game(config);
game.resize(400,700);
};
Note this only changes the canvas size, nothing else.

how to set fabric js to view only?

I'm trying to display a previously saved drawing in Fabric.js, and then turn it into a view only, no editing mode. Here's what I have:
canvas1.loadFromJSON(cavasData, canvas1.renderAll.bind(canvas1));
canvas1.isDrawingMode = false;
canvas1.deactivateAll();
canvas1.selection = false;
But it isn't untouchable. Displays fine, but objects are still select-able and change-able.
When loading from json you should disable object selection,
canvas.loadFromJSON(json, canvas.renderAll.bind(canvas), function(o, object) {
object.set('selectable', false);
});
FIDDLE
The easiest and most stable way is to set pointer-events css property on a parent HTML Element:
.locked {
pointer-events: none;
}
To set this property from javascript:
let canvasElement = document.getElementsByClassName("canvas-container")[0]
canvasElement.classList.add("locked")
To unlock canvas:
canvasElement.classList.remove("locked")

How to set a PNG file in a Gnome Shell extension for St.Icon

My Gnome Shell extension has a folder 'icons' and there is an icon file called 'MyIcon.png' inside. I want to take it as a parameter to an St.Icon object.
let icon = new St.Icon({ /* I need what to code here*/ });
Thanks for any help.
Selcuk.
Here is a solution for GnomeShell v3.8.4:
const St = imports.gi.St;
const Me = imports.misc.extensionUtils.getCurrentExtension();
const Gio = imports.gi.Gio;
let gicon = Gio.icon_new_for_string(Me.path + "/icons/my_icon.png");
icon = new St.Icon({ gicon });
Stumbled upon the very same problem yesterday.
You have to do two things:
Step 1: add this code to you "stylesheet.css"
.your-icon-name {
background-image: url("icons/MyIcon.png");
background-size: 20px;
height: 20px;
width: 20px;
}
'background-size', 'height' and 'width' are just there to prescale the image, they can be left out to keep the original size.
Step 2: Write this in your .js file:
let icon = new St.Icon({style_class: 'your-icon-name'});
Here is an example of how to do it:
_getIconImage: function() {
let icon_file = this._path + "/icons/icon.png";
let file = Gio.file_new_for_path(icon_file);
let icon_uri = file.get_uri();
return St.TextureCache.get_default().load_uri_async(icon_uri, 64, 64);
},
myIcon = _getIconImage();
For anyone wanting to add their extension's icon/ (or images/, etc.) sub directory to the search path in order to use CSS styles here is what is working for me:
function init(metadata) {
// ...
let theme = imports.gi.Gtk.IconTheme.get_default();
theme.append_search_path(metadata.path + "/icons");
// ...
}

Passing data between views in Durandal

I have a view with a grid from where I launch a different view in a new window. The parent view needs to pass data to the child view and it cannot be passed as a query string since it is pretty large. I cannot use a native modal. I am just using window.open and have not created a custom modal context.Things that I have tried
1. Tried populating hidden field elements in the new window using something like the code below
var popup = window.open('/#/viewname');
popup.onload=function(){document.findbyid('hiddenfield')= newvalue };
I am using a splash page to display loading in durandal , onload of the new window the router is still navigating and I get the splash page document
2. Tried requiring the parent view in the child view and then grabbing the parent view data using something like the code below
define(['viewmodels/parent'],function ('parent'){
function activate(){
localvariablevalue= parent.observable;
}
return{
activate:activate
}
});
I get parent.observable as undefined. More than likely it is out of context as this is a new window.
3.Tried a singleton global.js file and requiring it in both parent and child but I think that is also losing context as I get undefined even with that approach
Any ideas on how to achieve this.. if it can be achieved using v 1.2 ? If it can be achieved by creating a custom Modal context then can someone give some pointers on how to define the addContext function for a new window .
By using window.open an independent instance of a #/viewname SPA gets created that has no relation to the first SPA. If you really have to open a new window than you'd need to look into other ways to exchange information between windows like e.g. localstorage.
But you're probably better off rethinking the requirements and see if you can't achieve the goal with modals instead.
Edit based on comment: That has nothing to do with Durandal loosing context, it's about window.open not opening in the same session. see e.g. window.open doesn't open in same session
Edit2 As an alternative you might use the param #/viewname in the second windows vm.activate to retrieve the data either from the server directly (if applicable) or via webstorage e.g. localstorage or sessionstorage from the first browser.
Edit3 I did a quick test do see if that can be accomplished using window.open alone. Tested in Firefox/Firebug. I would still recommend to checkout the links to webstorage to ensure that this is working cross browser.
Go to http://dfiddle.github.io/dFiddle-1.2/#/, open up a console and create a global var:
window.myVar = {complex: true};
create a new window
var myWindow = window.open('http://dfiddle.github.io/dFiddle-1.2/#/view-composition');
go back first console and create a property myvar on myWindow
myWindow.myVar = myVar;
change to the console of the second window and check
myVar // output {complex: true}
The alternative that I was suggesting uses the unique hash value that you pass in to the second window and then either retrieves the complex data (that can be added as query string) as part of the activate callback. The data could be stored on the server or via webstorage.
Rainer thanks for trying to help out. The following code will generate maximised modals. At this stage I am resigning to this solution and passing my data as activation in showModal. It is an exact copy of the default modal context with minor css variations.But posting it if it helps someone out.
1. CSS
.modalHostMax {
top: 0px;
left: 0px;
right: 0px;
bottom: 0px;
position: fixed;
opacity: 0;
-webkit-backface-visibility: hidden;
-webkit-transition: opacity 0.1s linear;
-moz-transition: opacity 0.1s linear;
-o-transition: opacity 0.1s linear;
transition: opacity 0.1s linear;
}
.modalMax {
width: 100%;
height: 100%;
}
2.New modal context
var addContext = function (contextName) {
modalDialog.addContext(contextName, {
blockoutOpacity: .2,
removeDelay: 200,
addHost: function (modal) {
var body = $('body');
var blockout = $('<div class="modalBlockout"></div>')
.css({ 'z-index': modalDialog.getNextZIndex(), 'opacity': this.blockoutOpacity })
.appendTo(body);
var host = $('<div class="modalHostMax"></div>')
.css({ 'z-index': modalDialog.getNextZIndex() })
.appendTo(body);
modal.host = host.get(0);
modal.blockout = blockout.get(0);
if (!modalDialog.isModalOpen()) {
modal.oldBodyMarginRight = $("body").css("margin-right");
var html = $("html");
var oldBodyOuterWidth = body.outerWidth(true);
var oldScrollTop = html.scrollTop();
$("html").css("overflow-y", "hidden");
var newBodyOuterWidth = $("body").outerWidth(true);
body.css("margin-right", (newBodyOuterWidth - oldBodyOuterWidth + parseInt(modal.oldBodyMarginRight)) + "px");
html.scrollTop(oldScrollTop); // necessary for Firefox
$("#simplemodal-overlay").css("width", newBodyOuterWidth + "px");
}
},
removeHost: function (modal) {
$(modal.host).css('opacity', 0);
$(modal.blockout).css('opacity', 0);
setTimeout(function () {
$(modal.host).remove();
$(modal.blockout).remove();
}, this.removeDelay);
if (!modalDialog.isModalOpen()) {
var html = $("html");
var oldScrollTop = html.scrollTop(); // necessary for Firefox.
html.css("overflow-y", "").scrollTop(oldScrollTop);
$("body").css("margin-right", modal.oldBodyMarginRight);
}
},
afterCompose: function (parent, newChild, settings) {
var $child = $(newChild);
var width = $child.width();
var height = $child.height();
$child.attr('class', 'modalMax');
$(settings.model.modal.host).css('opacity', 1);
if ($(newChild).hasClass('autoclose')) {
$(settings.model.modal.blockout).click(function () {
settings.model.modal.close();
});
}
$('.autofocus', newChild).each(function () {
$(this).focus();
});
}
});
};
3. In your target view make sure that the container min-height is set to $(document).height()
4. To use just set custom context by calling addContext('yourcontextname').Then create your modal - app.showModal('viewname',{data},'yourcontextname')

Resources