/* This file is part of Warzone 2100. Copyright (C) 2008 Freddie Witherden Copyright (C) 2008 Elio Gubser Copyright (C) 2008 Warzone Resurrection Project Warzone 2100 is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Warzone 2100 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Warzone 2100; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include "window.h" static windowVtbl vtbl; static vector *windowVector = NULL; static int screenWidth = -1, screenHeight = -1; static const float borderRadius = 20; const classInfo windowClassInfo = { &widgetClassInfo, "window" }; static void windowInitVtbl(window *self) { static bool initialised = false; if (!initialised) { // Copy our parents vtable into ours vtbl.widgetVtbl = *(WIDGET(self)->vtbl); // Overload some of our parents methods vtbl.widgetVtbl.destroy = windowDestroyImpl; vtbl.widgetVtbl.addChild = windowAddChildImpl; vtbl.widgetVtbl.doLayout = windowDoLayoutImpl; vtbl.widgetVtbl.doDraw = windowDoDrawImpl; vtbl.widgetVtbl.doDrawMask = windowDoDrawMaskImpl; vtbl.widgetVtbl.getMinSize = windowGetMinSizeImpl; vtbl.widgetVtbl.getMaxSize = windowGetMaxSizeImpl; initialised = true; } // Replace our parents vtable with our own WIDGET(self)->vtbl = &vtbl.widgetVtbl; // Set our vtable self->vtbl = &vtbl; } static void windowCreateWindowPattern(window *self, int w, int h) { // First clean up our current pattern (if any) if (self->windowPattern) { cairo_pattern_destroy(WINDOW(self)->windowPattern); } self->windowPattern = cairo_pattern_create_linear(0, 0, 0, h); cairo_pattern_add_color_stop_rgba(self->windowPattern, 0, 0.000000, 0.000000, 0.235294, 0.75); cairo_pattern_add_color_stop_rgba(self->windowPattern, 0.2, 0.176470, 0.176470, 0.372549, 0.8); cairo_pattern_add_color_stop_rgba(self->windowPattern, 0.6, 0.176470, 0.176470, 0.372549, 0.7); cairo_pattern_add_color_stop_rgba(self->windowPattern, 1, 0.176470, 0.176470, 0.372549, 0.7); } static bool windowResizePatternCallback(widget *self, const event *evt, int handlerId, void *userData) { // Make sure this is a resize event assert(evt->type == EVT_RESIZE); // Re-create the window pattern windowCreateWindowPattern(WINDOW(self), self->size.x, self->size.y); return true; } void windowInit(window *self, const char *id, int w, int h) { // Init our parent widgetInit(WIDGET(self), id); // Patterns initialisation self->windowPattern = NULL; // Ensure the pattern is re-created whenever we are resized widgetAddEventHandler(WIDGET(self), EVT_RESIZE, windowResizePatternCallback, NULL, NULL); // Prepare our vtable windowInitVtbl(self); // Set our type WIDGET(self)->classInfo = &windowClassInfo; // Make sure we have a window list to add the window to assert(windowVector); // Add ourselves to the window list vectorAdd(windowVector, self); // Set our size to (w,h) widgetResize(WIDGET(self), w, h); // Mask for exact mouse events widgetEnableMask(WIDGET(self)); } void windowDestroyImpl(widget *self) { int i; // Remove ourself from the window list for (i = 0; i < vectorSize(windowVector); i++) { // If the window matches, remove it from the vector if (vectorAt(windowVector, i) == self) { vectorRemoveAt(windowVector, i); } } // Remove our pattern cairo_pattern_destroy(WINDOW(self)->windowPattern); // Call our parents destructor widgetDestroyImpl(self); } bool windowAddChildImpl(widget *self, widget *child) { // We can only hold a single child, no more (use a container!) assert(vectorSize(self->children) < 1); // Call the normal widgetAddChild method directly return widgetAddChildImpl(self, child); } bool windowDoLayoutImpl(widget *self) { /* * Position our (only) child at (0,0) making it as big as possible. */ if (vectorSize(self->children)) { widget *child = vectorHead(self->children); const size minSize = widgetGetMinSize(child); const size maxSize = widgetGetMaxSize(child); // Make sure we are large enough to hold the child if (minSize.x > self->size.x || minSize.y > self->size.y) { return false; } // Position the widget at (0,0) widgetReposition(child, 0, 0); // Resize it so that it is as large as possible widgetResize(child, MIN(self->size.x, maxSize.x), MIN(self->size.y, maxSize.y)); } return true; } void windowDoWindowPath(widget *self, cairo_t *cr) { size ourSize = WIDGET(self)->size; // Do the rounded rectangle cairo_arc_negative(cr, borderRadius, borderRadius, borderRadius, -M_PI_2, -M_PI); cairo_line_to(cr, 0, ourSize.y-borderRadius); cairo_arc_negative(cr, borderRadius, ourSize.y-borderRadius, borderRadius, M_PI, M_PI_2); cairo_line_to(cr, ourSize.x-borderRadius, ourSize.y); cairo_arc_negative(cr, ourSize.x-borderRadius, ourSize.y-borderRadius, borderRadius, M_PI_2, 0); cairo_line_to(cr, ourSize.x, borderRadius); cairo_arc_negative(cr, ourSize.x-borderRadius, borderRadius, borderRadius, 0, -M_PI_2); cairo_close_path(cr); } void windowDoDrawImpl(widget *self) { // Get drawing context cairo_t *cr = WIDGET(self)->cr; // Select window gradient cairo_set_source(cr, WINDOW(self)->windowPattern); // Do the rounded rectangle path windowDoWindowPath(self, cr); // Finally fill the path cairo_fill(cr); } void windowDoDrawMaskImpl(widget *self) { // Get the mask context cairo_t *cr = WIDGET(self)->maskCr; // Do the rounded rectangle path windowDoWindowPath(self, cr); // We don't have to specify the color, it's already set in our parent cairo_fill(cr); } size windowGetMinSizeImpl(widget *self) { const size minSize = { 1.0f, 1.0f }; return minSize; } size windowGetMaxSizeImpl(widget *self) { // Note the int => float conversion const size maxSize = { INT16_MAX, INT16_MAX }; return maxSize; } void windowSetWindowVector(vector *v) { // Make sure the vector is valid assert(v != NULL); windowVector = v; } vector *windowGetWindowVector() { return windowVector; } void windowHandleEventForWindowVector(const event *evt) { int i; // Make sure the vector is valid assert(windowVector != NULL); // Pass the event to each of the windows for (i = 0; i < vectorSize(windowVector); i++) { widgetHandleEvent(vectorAt(windowVector, i), evt); } } void windowSetScreenSize(int w, int h) { screenWidth = w; screenHeight = h; } void windowRepositionFromScreen(window *self, hAlign hAlign, int xOffset, vAlign vAlign, int yOffset) { int x = 0, y = 0; size ourSize = WIDGET(self)->size; // Ensure the screen width and height have been set assert(screenWidth != -1 && screenHeight != -1); switch (hAlign) { case LEFT: x = 0; break; case CENTRE: x = screenWidth / 2 - ourSize.x / 2; break; case RIGHT: x = screenWidth; // Transform xOffset so that +ve moves us away from the right xOffset = -xOffset; break; } switch (vAlign) { case TOP: y = 0; break; case MIDDLE: y = screenHeight / 2 - ourSize.y / 2; break; case BOTTOM: x = screenHeight; // Transform yOffset so that +ve moves us away from the bottom yOffset = -yOffset; break; } // Take offsets into account x += xOffset; y += yOffset; // Reposition widgetReposition(WIDGET(self), x, y); } void windowRepositionFromAnchor(window *self, const window *anchor, hAlign hAlign, int xOffset, vAlign vAlign, int yOffset) { int x = 0, y = 0; size anchorSize = WIDGET(anchor)->size; point anchorPos = WIDGET(anchor)->offset; size ourSize = WIDGET(self)->size; switch (hAlign) { case LEFT: x = anchorPos.x - ourSize.x; // Transform xOffset so that +ve moves us away from anchorPos.x xOffset = -xOffset; break; case CENTRE: x = anchorPos.x + (anchorSize.x / 2 - ourSize.x / 2); break; case RIGHT: x = anchorPos.x + anchorSize.x; break; } switch (vAlign) { case TOP: y = anchorPos.y; break; case MIDDLE: y = anchorPos.y + (anchorSize.x / 2 - ourSize.y / 2); break; case BOTTOM: y = anchorPos.y + anchorSize.y; break; } // Take the offsets into account x += xOffset; y += yOffset; // Reposition widgetReposition(WIDGET(self), x, y); }