I am writing an info-screen program. I created a full-screen widget and draw contents onto it.
In order to extend the life cycle of the TFT-display device, I want to implement a pixel-shifting feature. With other words, in every X minutes, I shift the screen to left/right/top/down for Y pixels.
My approach is as follows:
I use two layers (two QWidget).
I paint contents on the top layer.
When a pixel-shifting is performed, I just move the top layer for specified offset.
And then fill a background color to the bottom layer.
However, I found a problem:
If I move up the top layer for 10 pixels, the 10-pixel-content goes out of the screen. But when I move this layer down for 10 pixels. The 10-pixel-content will not be updated, it is gone.
How can I keep these 10-pixel-content? Is there any magic widget flag to solve this problem?
UPDATE 1:
The code is written in language D, but it is easy to understand:
class Canvas: QWidget
{
private QPixmap content;
this(QWidget parent)
{
super(parent);
setAttribute(Qt.WA_OpaquePaintEvent, true);
}
public void requestForPaint(QPixmap content, QRegion region)
{
this.content = content;
update(region);
}
protected override void paintEvent(QPaintEvent event)
{
if (this.content !is null)
{
QPainter painter = new QPainter(this);
painter.setClipping(event.region);
painter.fillRect(event.region.boundingRect, new QColor(0, 0, 0));
painter.drawPixmap(event.region.rect, this.content);
this.content = null;
painter.setClipping(false);
}
}
}
class Screen: QWidget
{
private Canvas canvas;
this()
{
super(); // Top-Level widget
setAutoFillBackground(True);
this.canvas = new Canvas(this);
showFullScreen();
}
public void requestForPaint(QPixmap content, QRegion region)
{
this.canvas.requestForPaint(content, region);
}
private updateBackgroundColor(QColor backgroundColor)
{
QPalette newPalette = palette();
newPalette.setColor(backgroundRole(), backgroundColor);
setPalette(newPalette);
}
public shiftPixels(int dx, int dy)
{
this.canvas.move(dx, dy);
updateBackgroundColor(new QColor(0, 0, 0)); // Just a demo background color
}
}
Screen screen = new Screen;
screen.requestForPaint(some_content, some_region);
screen.shiftPixels(0, -10);
screen.shiftPixels(0, 10);
Looking at the code, my first guess is that your region might be wrong. Try repainting the whole widget each time, and see if that solves the missing 10 pixel problem. If it does, then try working out why your region isn't covering the newly exposed portion.
One possibility along those lines: I notice in your Screen::requestForPaint method that you directly call the Canvas::requestForPaint without doing anything with the region. In Qt, the coordinates for anything like that are often assumed to be local, so if you don't account for the current position of the canvas widget, you might get an incorrect region.
Why not setting the position of the widget directly...? Another options might be using QPainter::translate(-1,-1) or something similar.
Related
(This is my first post, sorry if I do something wrong...)
I am writing a program in Vala with which one can design a classroom.
I have decided to use GTK for the GUI (Vala integrates well with this),
and Cairo to draw the classroom diagram (GTK comes with this by default).
I have created a 'classroom' class (a subclass of Gtk.DrawingArea),
which currently should just display a square:
public class Classroom : DrawingArea
{
private delegate void DrawMethod();
public Classroom()
{
this.draw.connect((widget, context) => {
return draw_class(widget, context, context.stroke);
});
}
bool draw_class(Widget widget, Context context, DrawMethod draw_method)
{
context.set_source_rgb(0, 0, 0);
context.set_line_width(8);
context.set_line_join (LineJoin.ROUND);
context.save();
context.new_path();
context.move_to(10, 10);
context.line_to(30, 10);
context.line_to(30, 30);
context.line_to(10, 30);
context.line_to(10, 10);
context.close_path();
draw_method(); // Actually draw the lines in the buffer to the widget
context.restore();
return true;
}
}
I have also created a class for my application:
public class SeatingPlanApp : Gtk.Application
{
protected override void activate ()
{
var root = new Gtk.ApplicationWindow(this);
root.title = "Seating Plan";
root.set_border_width(12);
root.destroy.connect(Gtk.main_quit);
var grid = new Gtk.Grid();
root.add(grid);
/* Make our classroom area */
var classroom = new Classroom();
grid.attach(classroom, 0, 0, 1, 1);
//root.add(classroom);
root.show_all();
}
public SeatingPlanApp()
{
Object(application_id : "com.github.albert-tomanek.SeatingPlan");
}
}
And this is my main function:
int main (string[] args)
{
return new SeatingPlanApp().run(args);
}
I put my classroom widget into a Gtk.Grid, my layout widget of choice.
When I compile my code and run it, I get a blank window:
However, if I do not use Gtk.Grid, and just add my classroom using root.add() (which I have commented out), the classroom widget is displayed correctly:
Why does my widget not show up when adding it using Gtk.Grid?
What can I do to fix this?
The problem is that the cell is sized 0x0 pixels, because the grid doesn't know how much space your drawing area actually needs.
A simple solution is to just request some fixed size, try this:
var classroom = new Classroom();
classroom.set_size_request (40, 40);
PS: I got this idea by looking at other similar questions on SO, particularly this one.
I'm displaying some information to the user in QScrollArea.
The user should have seen all contents, before she can proceed (at least the content should have been scrolled to the end)
How could I detect this in an easily?
Is the reimplementing of virtual void scrollContentsBy (int dx,int dy) the only way?
EDIT
I was able to solve it, but had to use some workarounds:
Scroll-action value sent by the signal actionTriggered(int) had never the value QAbstractSlider::SliderToMaximum (Qt4.8, Windows 7). So I've checked manually, if the slider value is close to maximum.
Even if scroll-bar widget was dragged by mouse till the bottom, the value of the scroll-bar is never the maximum. Only if the scroll-bar widget is moved by any other event such as arrow-down or mouse wheel, the value may become maximum. I've work-arounded it with recheckPosition()
I hope, there are better solutions.
void NegativeConfirmation::recheckPosition()
{
processScrollAction(1);
}
void NegativeConfirmation::processScrollAction( int evt)
{
if ( evt == QAbstractSlider::SliderToMaximum) // Have not managed to receive this action
{
ui->bConfirm->setEnabled(true);
}
//Another approach
QWidget * sw = ui->scrollArea->widget();
if ( sw ) //any content at all ?
{
QScrollBar * sb = ui->scrollArea->verticalScrollBar();
if ( sb )
{
int sbv = sb->value();
int sbm = sb->maximum()-10;
if ( sbm>0 && sbv >= sbm )
{
ui->bConfirm->setEnabled(true);
}
else
{
QTimer::singleShot(1000, this, SLOT(recheckPosition()));
}
}
}
}
QScrollArea inherits QAbstractSlider which provides this signal: -
void QAbstractSlider::actionTriggered(int action)
Where action can be QAbstractSlider::SliderToMaximum.
I expect you can connect to the this signal and test when the action is QAbstractSlider::SliderToMaximum, representing that the user has scrolled to the bottom.
This is a follow up to my last question:
How can I draw a circle to the screen with PlayN?
For my simple case, I want to programmatically create a single colored circle and move it across a 2-D plain (doesn't need to use box2d lib).
A real-world example would likely involve animating several circles. Two real-world examples for this case (sorry, I had to remove the links -- not enough karma!):
Browsmos for Chrome
Ants AI Challenge
It was suggested in response to my last question that I would want to use the ImmediateLayer class, so I am looking to understand how to properly incorporate this into my game loop.
Here's is my code sample:
public class SimpleCircleAnimation implements Game {
// Surface
private GroupLayer rootLayer;
private ImmediateLayer surface;
private Canvas canvas;
private Circle circle;
private CanvasImage circleImage;
#Override
public void init() {
// create root layer
rootLayer = graphics().rootLayer();
// a simple circle object
int circleX = 0; int circleY = 0;
int circleRadius = 20;
circle = new Circle(circleX, circleY, circleRadius);
// create an immediate layer and add to root layer
ImmediateLayer circleLayer = graphics().createImmediateLayer(new ImmediateLayer.Renderer() {
public void render (Surface surf) {
circleImage = graphics().createImage(circle.radius*2, circle.radius*2);
canvas = circleImage.canvas();
canvas.setFillColor(0xff0000eb);
canvas.fillCircle(circle.radius, circle.radius, circle.radius);
surf.drawImage(circleImage, circle.x, circle.y);
}
});
rootLayer.add(circleLayer);
}
#Override
public void paint(float alpha) {
}
#Override
public void update(float delta) {
// move circle
int newX = circle.x + 4; int newY = circle.y + 4;
circle.setPoint(newX, newY);
}
#Override
public int updateRate() {
return 25;
}
}
This successfully moves the circle diagonally down the screen from left to right. A couple questions:
Is this implemented properly?
In the case of multiple animated circles, is the idea with ImmediateLayer that you would create a circle image for each circle within the Renderer callback? Or would you perhaps create an Immediate Layer for each circle and add those to the root layer?
I would not use ImmediateLayer wiht render (Surface surf) adapter. Here u have, inside the render method creation of an image
circleImage = graphics().createImage(circle.radius*2, circle.radius*2);
just put this in the paint method
surf.drawImage(circleImage, circle.x, circle.y);
using the normal layer and u should be fine
Painting is done in paint method, and do not put calculations there
Update is for calculations, and physics oriented stuff
I discovered a detailed practical example of ImmediateLayer usage in the Cute Game source within the PlayN Samples:
CuteGame.java (code.google.com)
I have a QWidget window, which contains a QSplitter. I have a QStackedLayout (within a QWidget container) on both the left and right of the splitter. The main application resides in the left region, which can trigger QWebViews to appear on the right. When I remove the right QWebView, the Window attempts to resize, but stays at the width of the QWebView. When I try to drag the window, it snaps to position around the left side of the splitter.
I want the window to snap back to only show the left side of the splitter on removing the last widget from the right QStackedWidget. I've tried various incantations, but I don't know Qt very well, and am attempting to learn as I go.
The below code is in Java, using the Jambi wrappers. Concepts should be the same in C++, and I'm able to translate any other language binding to Jambi if necessary.
I attempt to set the window minimum width on add and remove of widgets to the RightStackedLayout, but this breaks the splitter handle in some cases. I consider this a hack workaround. If anyone can point me in the right direction of having changes to the right side of the splitter update the top level window, I'll be very grateful.
// The main Window class
public Window() { // #QWidget
super();
this.setContentsMargins(0, 0, 0, 0);
QHBoxLayout mainLayout = new QHBoxLayout(this);
mainLayout.setContentsMargins(0, 0, 0, 0);
splitter = new QSplitter();
QWidget leftContainer = new QWidget();
QWidget rightContainer = new QWidget();
leftLayout = new QStackedLayout();
rightLayout = new RightStackedLayout();
leftContainer.setLayout(leftLayout);
rightContainer.setSizePolicy(new QSizePolicy(QSizePolicy.Policy.Expanding,QSizePolicy.Policy.Expanding));
rightContainer.setLayout(rightLayout);
splitter.addWidget(leftContainer);
splitter.addWidget(rightContainer);
splitter.setStretchFactor(0, 0); // do not resize left
splitter.setStretchFactor(1, 1); // fully resize right
splitter.setWindowState(WindowState.WindowMaximized);
mainLayout.addWidget(splitter);
}
public void denyWidthChange() { // when right side is closed, prevent user from resizing horizontally
this.setSizePolicy(new QSizePolicy(QSizePolicy.Policy.Fixed,QSizePolicy.Policy.Preferred));
this.splitter.setSizePolicy(new QSizePolicy(QSizePolicy.Policy.Fixed,QSizePolicy.Policy.Preferred));
this.splitter.updateGeometry();
this.updateGeometry();
}
public void allowWidthChange() { // when right side open, allow user to expand horizontally
this.setSizePolicy(new QSizePolicy(QSizePolicy.Policy.Expanding,QSizePolicy.Policy.Expanding));
this.splitter.setSizePolicy(new QSizePolicy(QSizePolicy.Policy.Expanding,QSizePolicy.Policy.Expanding));
this.splitter.updateGeometry();
this.updateGeometry();
}
public void adjustSizes(int w, int h) {
this.resize(w, h);
this.splitter.resize(w, h);
}
// The RightStackedLayout
public void add(QWidget widget) {
Application.window.allowWidthChange();
Application.window.setMinimumWidth(((WebPane)widget).minWidth()+this.rememberWidth); // left side + right side
QWidget current = this.currentWidget();
if (current != null) {
this.rememberWidth = current.size().width(); // remember the user resize preferences
}
int idx = this.indexOf(widget);
if (idx == -1) {
widget.setMinimumWidth(this.rememberWidth);
this.addWidget(widget);
}
this.setCurrentWidget(widget);
}
public void remove(QWidget widget) {
Application.window.allowWidthChange();
this.removeWidget(widget);
this.update();
if (this.count() == 0) {
Log.debug("Last RightWebPane Closing: Shrinking the window");
this.rememberWidth = widget.size().width();
this.activate();
//((QWidget)this.parent()).resize(0, 0);
Application.window.setMinimumWidth(((WebPane)widget).minWidth());
Application.window.adjustSizes(0,Application.window.height());
Application.window.denyWidthChange();
}
}
While I still haven't got a fully functional resizing policy happening, I'm most of the way and figured out the problem I was having had to do with not taking the splitter.handleWidth() into account when attempting to calculate the total width.
I think there is a simple solution to this question, just not simple enough for me to find it.
Question:
How do you constrain a TitleWindow in Flex 3 from being dragged off the screen/stage? Is there a way to restrict the TitleWindow to the viewing area?
Example: Let's say I have an application that take 100% of the screen. Next, I create a TitleWindow via the PopUpManager. I can then proceed to click and hold (drag) that window off the screen, then release the mouse button. That window is now lost off-screen somewhere. Is there a way to keep the window from being dragged beyond the viewing area?
Thanks for the help in advance.
this is a very old post, but here's another way of doing it:
Whether you are extending the component or not, in the TitleWindow definition add the following line: move:"doMove(event)"
Import the Application library (import mx.core.Application;)
and add the doMove function:
private function doMove(event:Event):void
{//keeps TW inside layout
var appW:Number=Application.application.width;
var appH:Number=Application.application.height;
if(this.x+this.width>appW)
{
this.x=appW-this.width;
}
if(this.x<0)
{
this.x=0;
}
if(this.y+this.height>appH)
{
this.y=appH-this.height;
}
if(this.y<0)
{
this.y=0;
}
}
For flex 4 the answer is here: http://blog.flexexamples.com/2010/01/20/constraining-the-movement-on-a-spark-titlewindow-container-in-flex-4/
You can set its isPopUp property to false to prevent it from being dragged in the first place.
var popupWin:TitleWindow = PopUpManager.createPopUp(this, TitleWindow);
PopUpManager.centerPopUp(popupWin);
popupWin.isPopUp = false;
I don't know if the DragManager class in flex supports bounds checking, but if you really want to allow dragging but limit its bounds, you can still set isPopUp to false and implement the dragging code yourself so that the component never goes outside the limits specified by you. Check startDrag() method for an example. Bounds rectangle is the key.
Flex 4
<s:TitleWindow xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx"
windowMoving="windowMovingHandler(event)">
.
.
.
protected function windowMovingHandler(event:TitleWindowBoundsEvent):void
{
var appBounds:Rectangle = parentApplication.getBounds(DisplayObject(parentApplication));
if(!appBounds.containsRect(event.afterBounds)){
event.preventDefault();
}
}
// for better precision, corect appBounds manualy, or, instead of "parentApplication.getBounds..." create new rectangle of your application size
Subclass the TitleWindow and add a canvas over the title bar as a drag proxy. Then you can explicity call startDrag with a boundary rectangle.
This is pretty skeletal, but should put you on the path...
The reason for the proxy is you may get some weird behavior when you click the titleBar label if you don't have the canvas over it.
public class MyTitleWindow extends TitleWindow
{
public var titleBarOverlay:Canvas;
override protected function createChildren():void
{
super.createChildren();
if(!titleBarOverlay)
{
titleBarOverlay = new Canvas();
titleBarOverlay.width = this.width;
titleBarOverlay.height = this.titleBar.height;
titleBarOverlay.alpha = 0;
titleBarOverlay.setStyle("backgroundColor", 0x000000);
rawChildren.addChild(titleBarOverlay);
}
addListeners();
}
override protected function updateDisplayList(w:Number, h:Number):void
{
super.updateDisplayList(w, h);
titleBarOverlay.width = this.width;
titleBarOverlay.height = this.titleBar.height;
}
private function addListeners():void
{
titleBarOverlay.addEventListener(MouseEvent.MOUSE_DOWN, onTitleBarPress, false, 0, true);
titleBarOverlay.addEventListener(MouseEvent.MOUSE_UP, onTitleBarRelease, false, 0, true);
}
private function onTitleBarPress(event:MouseEvent):void
{
// Here you can set the boundary using owner, parent, parentApplication, etc.
this.startDrag(false, new Rectangle(0, 0, parent.width - this.width, parent.height - this.height));
}
private function onTitleBarRelease(event:Event):void
{
this.stopDrag();
}
}
You could simply override the move function and prevent "illegal" movement (it is called internally by the Panel drag management).
I think that you also should listen on stage resize, because reducing it (e.g. if the user resize the browser window) could send your popup out of stage even without actually moving it.
public class MyTitleWindow extends TitleWindow {
public function MyTitleWindow() {
// use a weak listener, or remember to remove it
stage.addEventListener(Event.RESIZE, onStageResize,
false, EventPriority.DEFAULT, true);
}
private function onStageResize(event:Event):void {
restoreOutOfStage();
}
override public function move(x:Number, y:Number):void {
super.move(x, y);
restoreOutOfStage();
}
private function restoreOutOfStage():void {
// avoid the popup from being positioned completely out of stage
// (use the actual width/height of the popup instead of 50 if you
// want to keep your *entire* popup on stage)
var topContainer:DisplayObjectContainer =
Application.application.parentDocument;
var minX:int = 50 - width;
var maxX:int = topContainer.width - 50;
var minY:int = 0;
var maxY:int = topContainer.height - 50;
if (x > maxX)
x = maxX
else if (x < minX)
x = minX;
if (y > maxY)
y = maxY
else if (y < minY)
y = minY;
}
}
In your TitleWindow's creationComplete handler add the following:
this.moveArea.visible=false;
This will do the job.
On the other hand, if you have a custom skin, you can remove the "moveArea" part. This should work, too.