I'm trying to display multiple cairo drawings overlapping each other:
extern crate cairo;
extern crate gio;
extern crate gtk;
use std::f64::consts::PI;
use gio::prelude::*;
use gtk::prelude::*;
use gtk::DrawingArea;
use std::env::args;
fn build_ui(application: >k::Application) {
let window = gtk::ApplicationWindow::new(application);
window.set_default_size(300, 300);
let overlay = gtk::Overlay::new();
// Draw first circle
let drawing_area1 = Box::new(DrawingArea::new)();
drawing_area1.connect_draw(|_, ctx| draw(ctx, 0.5, 0.4));
overlay.add(&drawing_area1);
// Draw second circle
let drawing_area2 = Box::new(DrawingArea::new)();
drawing_area2.connect_draw(|_, ctx| draw(ctx, 0.2, 1.0));
overlay.add(&drawing_area2);
window.add(&overlay);
window.show_all();
}
fn draw(ctx: &cairo::Context, width: f64, color: f64) -> Inhibit {
ctx.scale(300., 300.);
ctx.arc(0.5, 0.5, width, 0.0 * PI, 2.0 * PI);
ctx.set_source_rgba(color, 0.0, 0.0, 0.8);
ctx.fill_preserve();
Inhibit(false)
}
fn main() {
let application =
gtk::Application::new(Some("example.overlay"), Default::default())
.expect("Initialization failed...");
application.connect_activate(|app| {
build_ui(app);
});
application.run(&args().collect::<Vec<_>>());
}
Running this code gives me this warning:
(test_overlay_gtk:25534): Gtk-WARNING **: 19:12:05.573: Attempting to add a widget with type GtkDrawingArea to a GtkOverlay, but as a GtkBin subclass a GtkOverlay can only contain one widget at a time; it already contains a widget of type GtkDrawingArea
I understand that the overlay object can display only one of the drawing areas. I thought the overlay class is for exactly this purpose, to show overlapping widgets. I can't find a way to display the second overlapping drawing area.
add adds the widget to the overlay as the primary child - you can only have one of these. This is inherited from the container class in older versions of gtkmm (which I assume you're using) and is replaced by set_child in gtkmm 4 (which no longer inherits add from Gtk::Container).
add_overlay is the Gtk::Overlay specific method that allows you to add any number of widgets to be displayed on top of the child widget.
Try replacing your second add method with add_overlay and it should work.
First: I have no idea what I am doing here.
I simply asked Google for GtkOverlay and found this page: https://developer.gnome.org/gtk3/stable/GtkOverlay.html
The documentation for gtk_overlay_add_overlay says:
Adds widget to overlay.
The widget will be stacked on top of the main widget added with gtk_container_add().
So, apparently there is a main widget which you .add() and overlays which you .add_overlay().
For your example code: Can't you just draw the two drawings to the same overlay widget? Something like drawing_area1.connect_draw(|_, ctx| draw(ctx, 0.5, 0.4); draw(ctx, 0.2, 1.0));
Related
I'm trying to create a custom scrollable text area. I created a DrawingArea and a ScrollBar inside a Grid. I have attached the draw event of DrawingArea to this.on_draw method which simply looks at ScrollBar's value and moves the Cairo.Context appropriately before drawing the Pango.Layout.
The first problem is that this.on_draw is getting invoked whenever the ScrollBar is touched even though I have not registered any events with ScrollBar. How do I prevent this, or check this?
The second problem is that even though this.on_draw is invoked, the changes made to the Context is not displayed unless the ScrollBar value is near 0 or 100 (100 is the upper value of Adjustment). Why is this happening?
I did find out that if I connect the value_changed event of ScrollBar to a method that calls queue_redraw of DrawingArea, it would invoke this.on_draw and display it properly after it. But due to the second problem, I think this.on_draw is getting invoked too many times unnecessarily. So, what is the "proper" way of accomplishing this?
using Cairo;
using Gdk;
using Gtk;
using Pango;
public class Texter : Gtk.Window {
private Gtk.DrawingArea darea;
private Gtk.Scrollbar scroll;
private string text = "Hello\nWorld!";
public Texter () {
GLib.Object (type: Gtk.WindowType.TOPLEVEL);
Gtk.Grid grid = new Gtk.Grid();
this.add (grid);
var drawing_area = new Gtk.DrawingArea ();
drawing_area.set_size_request (200, 200);
drawing_area.expand = true;
drawing_area.draw.connect (this.on_draw);
grid.attach (drawing_area, 0, 0);
var scrollbar = new Gtk.Scrollbar (Gtk.Orientation.VERTICAL,
new Gtk.Adjustment(0, 0, 100, 0, 0, 1));
grid.attach (scrollbar, 1, 0);
this.darea = drawing_area;
this.scroll = scrollbar;
this.destroy.connect (Gtk.main_quit);
}
private bool on_draw (Gtk.Widget sender, Cairo.Context ctx) {
ctx.set_source_rgb (0.9, 0.9, 0.9);
ctx.paint ();
var y_offset = this.scroll.get_value();
stdout.printf("%f\n", y_offset);
ctx.set_source_rgb (0.25, 0.25, 0.25);
ctx.move_to(0, 100 - y_offset);
var layout = Pango.cairo_create_layout(ctx);
layout.set_font_description(Pango.FontDescription.from_string("Sans 12"));
layout.set_auto_dir(false);
layout.set_text(this.text, this.text.length);
Pango.cairo_show_layout(ctx, layout);
return false;
}
static int main (string[] args) {
Gtk.init (ref args);
var window = new Texter ();
window.show_all ();
Gtk.main ();
return 0;
}
}
Also, please point out any (possibly unrelated) mistake if you find one in the above code.
The part that you are missing is that a draw signal does not mean "redraw everything". Instead, GTK+ sets the clip region of the cairo context to the part that needs to be redrawn, so everything else you do doesn't have any effect. The cairo function cairo_clip_extents() will tell you what that region is. The queue_draw_area() method on GtkWidget will allow you to explicitly mark a certain area for drawing, instead of the entire widget.
But your approach to scrollbars is wrong anyway: you're trying to build the entire infrastructure from scratch! Consider using a GtkScrolledWindow instead. This automatically takes care of all the details of scrolling for you, and will give you the overlay scrollbars I mentioned. All you need to do is set the size of the GtkDrawingArea to the size you want it to be, and GtkScrolledWindow will do the rest. The best way to do this is to subclass GtkDrawingArea and override the get_preferred_height() and/or get_preferred_width() virtual functions (being sure to set both minimum and natural sizes to the sizes you want for that particular dimension). If you ever need to change this size later, call the queue_resize() method of GtkWidget. (You probably could get away with just using set_size_request(), but what I described is the preferred way of doing this.) Doing this also gives you the advantage of not having to worry about transforming your cairo coordinates; GtkScrolledWindow does this for you.
I'm trying to create a label decorator to add an icon to the top left of my file icons. I see that the little red X can be drawn off of the edge of the icon, but my radiation symbol is cut off at the edge.
l
#Override
public Image decorateImage(Image image, Object element) {
Image failureImg = Activator.imageDescriptorFromPlugin(IMAGE PATH).createImage();
GC gc = new GC(image);
gc.drawImage(failureImg, 0, 0, failureImg.getImageData().width, failureImg.getImageData().height,
0, 0, 11, 11);
gc.dispose();
return image;
}
Any ideas on how to draw outside of the bounds of the file icon?
It is easier to use a lightweight label decorator (implement ILightweightLabelDecorator and specify lightweight="true" in the extension point).
You can then add the decoration image with:
#Override
public void decorate(final Object element, final IDecoration decoration)
{
ImageDescriptor imageDescriptor = Activator.imageDescriptorFromPlugin(IMAGE PATH);
decoration.addOverlay(imageDescriptor, IDecoration.TOP_LEFT);
}
Since lightweight decorators are run in a background thread they also make the UI more responsive.
Note: Your code is creating Image objects and not arranging for them to be disposed - this leaks resource handles. The lightweight decorator does not have this issue.
For the purpose of mine gstreamer application I tought about simple loader before I give a handle of DrawingArea widget to sink element.The basic idea was to load an animated .gif inside Gtk.DrawingArea but I run on the problem with documentation.I found out about PixbufAnimation and I used it with Gtk.Image widget but the same logic doesn't work for Gtk.DrawingArea and since it doesn't have add method I don't know what to do so as my last resort I came here to get a help.
This is what I did with Gtk.Image:
from gi.repository import Gdk,Gtk,GdkPixbuf
class animatedWin(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self,width_request=640,height_request=480)
self.canvas=Gtk.Image()
self.add(self.canvas)
self.load_file()
self.connect("delete-event",self.Gtk.main_quit)
def load_file(self):
self.loader=GdkPixbuf.PixbufAnimation.new_from_file("loader.gif")
self.canvas.set_from_animation(self.loader)
app=animatedWin()
app.show_all()
Gtk.main()
is it possible to achieve the same thing with DrawingArea ?
DrawingArea like most widgets in gtk3 uses cairo for drawing on them. Cairo draws on surfaces using context. You can convert pixbuf into surface by
public Surface Gdk.cairo_surface_create_from_pixbuf (Pixbuf pixbuf, int scale, Window? for_window)
And back by
public Pixbuf? Gdk.pixbuf_get_from_surface (Surface surface, int src_x, int src_y, int width, int height)
(taken from valadoc.org)
Example code snippet from my drawing app (I'm learning Vala while I writing it, so it may not be best implementation):
private void on_scale (Gtk.Button button) { // on button press
var my_pixbuf = Gdk.pixbuf_get_from_surface (this.buf_surface, 0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
var tmp_surface = Gdk.cairo_surface_create_from_pixbuf (my_pixbuf, 2, null);
var ctx = this.ccc; //this.ccc is context of drawing surface
ctx.set_source_surface (tmp_surface, 0, 0);
ctx.paint();
drawing_area.queue_draw(); // ask drawing_area to redraw, on redraw I have function/method that will paint drawing_area widget surface with drawing surface
}
PS. see http://valadoc.org/#!api=cairo/Cairo for more info on cairo. As I see it, cairo used for vector graphics and pixbuf for raster.
In Glade (version 3.8.5 as I am targetting gtk 2.24), I have created a label that sits in a row of a vbox. I have set the background color of the label (in Attributes) to blue, and the label appears to fill the entire row. But the blue background only extends as far as the text extent, not the entire width of the label.
How can I make the blue bar extend to the edges of the panel?
I read a while ago that Gtk labels don't actually have a color of their own, but they take whatever color their background widget has. I don't remember the source of this piece of information, but I do remember the solution, which is to put the label in a Gtk EventBox and change the color of said EventBox. I tested this solution in my gtk project with good results.
This is the function I use to change the color of the EventBox, by the way I'm using slightly older versions of Gtk and Glade, and I'm using C++ so if you're working with C you'll have to find the C equivalents of every function:
void GuiUtil::changeColor(Gtk::Widget* widget, double r, double g, double b) {
Glib::RefPtr<Gdk::Colormap> colormap = widget->get_colormap();
Glib::RefPtr<Gtk::Style> style = widget->get_style()->copy();
// STATE_NORMAL (most of the time)
{
Gdk::Color color;
color.set_rgb_p(r,g,b);
colormap->alloc_color(color);
style->set_bg(Gtk::STATE_NORMAL, color);
}
// STATE_PRELIGHT (when mouse hovers)
{
Gdk::Color color;
color.set_rgb_p(r*0.9,g*0.9,b*0.9);
colormap->alloc_color(color);
style->set_bg(Gtk::STATE_PRELIGHT, color);
}
// STATE_ACTIVE (when clicked)
{
Gdk::Color color;
color.set_rgb_p(r*0.8,g*0.8,b*0.8);
colormap->alloc_color(color);
style->set_bg(Gtk::STATE_ACTIVE, color);
}
widget->set_style(style);
}
As far as I know there isn't a way of doing this with Glade only.
I'm wondering about the behavior of {Shape}.attr("fill","url({image.path})").
when applying a fill image to a shape:
public class AppMapCanvas extends Raphael {
public AppMapCanvas(int width, int height) {
super(width, height);
this.hCenter = width / 2;
this.vCenter = height / 2;
...
Rect rect = this.new Rect(hCenter, vCenter, 144, 40, 4);
rect.attr("fill", "url('../images/app-module-1-bg.png')"); // <--
...
}
}
The background image seem to teal accross the canvas behind the shape, thus gets weird positioning (an illustration snapshot is enclosed - i marked the original image borders in red).
This seem to resolve itself in the presence of an animation along a path (a mere path.M(0,0) is sufficiant).
How can i position the fill-image properly in the first place?
The proper way to do this from what I can understand would be to use an SVG pattern declaration to specify the portion and position of the image you would want to use. Then you would use that pattern to fill the rectangle element. Unfortunately, the Raphael javascript library doesn't have support for patterns... so there's no direct way to use an image to fill a rectangle.