ecere: Native Decorations: fixed broken system buttons of non-root windows
[sdk] / ecere / src / gui / skins / WindowsSkin.ec
1 namespace gui::skins;
2
3 #if defined(WIN32)
4 #define WIN32_LEAN_AND_MEAN
5 #define Method _Method
6 #include <windows.h>
7 #undef Method
8 #endif
9
10 import "Window"
11
12 #define BORDER       4
13 #define TOP          4
14 #define BOTTOM       4
15 #define CORNER       (BORDER * 2)
16 #define CAPTION      20
17 #define DEAD_BORDER  3
18 #define MIN_WIDTH    60
19 #define MIN_HEIGHT   3
20 #define BUTTON_OFFSET   2
21 #define NAME_OFFSET   2
22 #define NAME_OFFSETX  4
23
24 #define SB_WIDTH  16
25 #define SB_HEIGHT 16
26
27 #define STATUS_HEIGHT   18
28
29 #define GRADIENT_SMOOTHNESS 1.0f
30
31 #define TEXT_COLOR   black
32 /*
33
34 #define GRADIENT_DIRECTION horizontal
35
36 static ColorKey gradient[] =
37 {
38    { Color { 128, 128, 255}, 0.00f },
39    { Color { 254, 254, 254}, 0.60f },
40    { Color {   0,   0, 255}, 1.00f }
41 };
42 */
43
44 #define GRADIENT_DIRECTION vertical
45 static ColorKey gradient[] =
46 {
47    { ColorAlpha { 255, { 180, 200, 220} }, 0.00f },
48    { ColorAlpha { 255, { 255, 255, 255} }, 0.60f },
49    { ColorAlpha { 255, { 128, 128, 130} }, 1.00f }
50 };
51
52 static ColorKey gradientInactive[] =
53 {
54    { ColorAlpha { 255, { 160, 180, 200} },  0.00f },
55    { ColorAlpha { 255, { 220, 220, 220} }, 0.60f },
56    { ColorAlpha { 255, { 100, 100, 100} }, 1.00f }
57 };
58
59 /*
60 #define GRADIENT_DIRECTION horizontal
61 #define TEXT_COLOR         white
62 */
63 //#define TEXT_INACTIVE      Color { 212,208,200 }
64 #define TEXT_INACTIVE      Color { 40, 50, 60 }
65 /*
66 static ColorKey gradient[] =
67 {
68    { ColorAlpha { 255, Color {  10,   36, 106 } }, 0.00f },
69    { ColorAlpha { 255, Color { 166,  202, 240 } }, 1.00f }
70 };
71 static ColorKey gradientInactive[] =
72 {
73    { ColorAlpha { 255, Color { 128, 128, 128 } }, 0.00f },
74    { ColorAlpha { 255, Color { 192, 192, 192 } }, 1.00f }
75 };
76 */
77 char * cursorsBitmaps[] = 
78 {
79    "<:ecere>cursors/arrow.png",
80    "<:ecere>cursors/iBeam.png",
81    "<:ecere>cursors/cross.png",
82    "<:ecere>cursors/move.png",
83    "<:ecere>cursors/sizeNorthEastSouthWest.png",
84    "<:ecere>cursors/sizeNorthSouth.png",
85    "<:ecere>cursors/sizeNorthWestSouthEast.png",
86    "<:ecere>cursors/sizeWestEast.png",
87    "<:ecere>cursors/move.png"
88 };
89
90 static Point cursorsHotSpots[] =
91 {
92    { 0, 0 },
93    { 0, 0 },
94    { 8, 8 },
95    { 10, 10 },
96    { 8, 8 },
97    { 4, 10 },
98    { 7, 7 },
99    { 5, 0 }
100 };
101
102 static char * skinBitmaps[SkinBitmap] =
103 {
104    "<:ecere>elements/areaMinimize.png",
105    "<:ecere>elements/areaMaximize.png",
106    "<:ecere>elements/areaRestore.png",
107    "<:ecere>elements/areaClose.png"
108 };
109
110 class WindowsSkin : Skin
111 {
112    class_property(name) = "Windows";
113    class_property(selectionColor) = Color { 10, 36, 106 };
114    class_property(selectionText)  = (Color)white;
115    class_property(disabledFrontColor) = Color { 128,128,128 };
116    class_property(disabledBackColor) = (Color)white;
117
118    FontResource ::SystemFont()
119    {
120       return FontResource { faceName = "Tahoma", size = 8.25f };
121    }
122
123    FontResource ::CaptionFont()
124    {
125       return FontResource { faceName = "Tahoma", size = 8.25f, bold = true };
126    }
127
128    char * ::CursorsBitmaps(uint id, int * hotSpotX, int *hotSpotY, byte ** paletteShades)
129    {
130       *hotSpotX = cursorsHotSpots[id].x;
131       *hotSpotY = cursorsHotSpots[id].y;
132       *paletteShades = null;
133       return cursorsBitmaps[id];
134    }
135
136    BitmapResource ::GetBitmap(SkinBitmap id)
137    {
138       return BitmapResource { fileName = skinBitmaps[id] };
139    }
140
141    int ::VerticalSBW() { return SB_WIDTH; }
142    int ::HorizontalSBH() { return SB_HEIGHT; }
143 };
144
145
146 public class WindowsSkin_Window : Window
147 {
148    void GetDecorationsSize(MinMaxValue * w, MinMaxValue * h)
149    {
150       *w = *h = 0;
151
152       if(hasMenuBar && state != minimized)
153       {
154          *h += skinMenuHeight;
155       }
156       if(statusBar && state != minimized)
157       {
158          *h += STATUS_HEIGHT;
159       }
160
161       if(nativeDecorations && rootWindow == this)
162       {
163 #if defined(WIN32)
164          RECT rcClient, rcWindow;
165          GetClientRect(windowHandle, &rcClient);
166          GetWindowRect(windowHandle, &rcWindow);
167          *w += (rcWindow.right - rcWindow.left) - rcClient.right;
168          *h += (rcWindow.bottom - rcWindow.top) - rcClient.bottom;
169
170          // PrintLn(_class.name, " is at l = ", rcWindow.left, ", r = ", rcWindow.right);
171 #endif
172          return;
173       }
174       if((((BorderBits)borderStyle).deep || ((BorderBits)borderStyle).bevel) && state != minimized)
175       {
176          *w += 4;
177          *h += 4;
178       }
179       if(((BorderBits)borderStyle).sizable && (state == normal))
180       {
181          *w += 2 * BORDER;
182          *h += TOP + BOTTOM;
183       }
184       if(((BorderBits)borderStyle).fixed && (state != maximized || !parent.menuBar))
185       {
186          *h += CAPTION;
187          if(!((BorderBits)borderStyle).sizable || state == minimized)
188          {
189             *h += 2*DEAD_BORDER;
190             *w += 2*DEAD_BORDER;
191          }
192       }
193       if(((BorderBits)borderStyle).contour && !((BorderBits)borderStyle).fixed)
194       {
195          *w += 2;
196          *h += 2;
197       }
198    }
199
200    void SetWindowMinimum(MinMaxValue * mw, MinMaxValue * mh)
201    {
202       bool isNormal = (state == normal);
203       if(nativeDecorations && rootWindow == this) return;
204       if(((BorderBits)borderStyle).fixed && (state != maximized || !parent.menuBar))
205       {
206          *mw = MIN_WIDTH;
207          *mh = MIN_HEIGHT;
208       }
209       else
210          *mw = *mh = 0;
211       if(((BorderBits)borderStyle).sizable && isNormal)
212          *mw += 2*CORNER;
213       // GetDecorationsSize(window, mw, mh);
214
215       if(hasVertScroll)
216          *mw += SB_WIDTH;
217       if(hasHorzScroll)
218          *mh += SB_HEIGHT;
219       if(hasVertScroll && hasHorzScroll)
220       {
221          *mw += 2 * SB_WIDTH + SB_WIDTH;
222          *mh += 2 * SB_HEIGHT + SB_HEIGHT;
223          if(((BorderBits)borderStyle).sizable && isNormal)
224             *mw -= 2*CORNER;
225       }
226    }
227
228    void SetWindowArea(int * x, int * y, MinMaxValue * w, MinMaxValue * h, MinMaxValue * cw, MinMaxValue * ch)
229    {
230       bool isNormal = (state == normal);
231       MinMaxValue aw = 0, ah = 0;
232
233       *x = *y = 0;
234
235       if(hasMenuBar)
236       {
237          *y += skinMenuHeight;
238       }
239
240       GetDecorationsSize(&aw, &ah);
241
242       if(nativeDecorations && rootWindow == this)
243       {
244 #if defined(WIN32)
245          RECT rcWindow;
246          POINT client00 = { 0, 0 };
247          ClientToScreen(windowHandle, &client00);
248          GetWindowRect(windowHandle, &rcWindow);
249          *x += client00.x - rcWindow.left;
250          *y += client00.y - rcWindow.top;
251 #endif
252       }
253       else
254       {
255          // Compute client area start
256          if(((BorderBits)borderStyle).deep || ((BorderBits)borderStyle).bevel)
257          {
258             *x += 2;
259             *y += 2;
260          }
261
262          if(((BorderBits)borderStyle).sizable && isNormal)
263          {
264             *x += BORDER;
265             *y += TOP;
266          }
267
268          if(((BorderBits)borderStyle).fixed && (state != maximized || !parent.menuBar))
269          {
270             *y += CAPTION;
271             if(!((BorderBits)borderStyle).sizable || state == minimized)
272             {
273                *y += DEAD_BORDER;
274                *x += DEAD_BORDER;
275             }
276          }
277
278          if(((BorderBits)borderStyle).contour && !((BorderBits)borderStyle).fixed)
279          {
280             *x += 1;
281             *y += 1;
282          }
283       }
284
285       // Reduce client area
286       *cw = *w - aw;
287       *ch = *h - ah;
288
289       *cw = Max(*cw, 0);
290       *ch = Max(*ch, 0);
291    }
292
293    void ShowDecorations(Font captionFont, Surface surface, char * name, bool active, bool moving)
294    {
295       bool isNormal = (state == normal);
296       int top = 0, border = 0, bottom = 0;
297
298       if(nativeDecorations && rootWindow == this) return;
299
300       if(state == minimized)
301          top = border = bottom = DEAD_BORDER;
302       else if(((BorderBits)borderStyle).sizable)
303       {
304          top = isNormal ? TOP : 0;
305          border = isNormal ? BORDER : 0;
306          bottom = BOTTOM;
307       }
308       else if(((BorderBits)borderStyle).fixed)
309       {
310          top = DEAD_BORDER;
311          border = DEAD_BORDER;
312          bottom = DEAD_BORDER;
313       }
314       else if(((BorderBits)borderStyle).contour)
315       {
316          top = 1;
317          border = 1;
318          bottom = 1;
319       }
320
321       if(((BorderBits)borderStyle).deep || ((BorderBits)borderStyle).bevel)
322       {
323          int deepTop = 0, deepBottom = 0, deepBorder = 0;
324          if(((BorderBits)borderStyle).contour)
325          {
326             deepBorder = border;
327             deepTop = (((BorderBits)borderStyle).fixed && (state != maximized || !parent.menuBar)) ? (top + CAPTION) : top;
328             deepBottom = (((BorderBits)borderStyle).sizable && isNormal) ? bottom : border;
329          }
330
331          surface.Bevel(((BorderBits)borderStyle).bevel ? false : true, deepBorder, deepTop, 
332             size.w - deepBorder - deepBorder, size.h - deepBottom - deepTop);
333       }
334
335       if(((BorderBits)borderStyle).fixed && (state != maximized || !parent.menuBar))
336       {
337          if(state != maximized || !((BorderBits)borderStyle).sizable)
338          {
339             // Frame for ES_CAPTION windows
340             surface.Bevel(false, 0, 0, size.w, size.h);
341             surface.SetForeground(activeBorder);
342             surface.Rectangle(2, 2, size.w-3, size.h-3);
343
344             // Resizeable frame is 1 pixel thicker 
345             if(((BorderBits)borderStyle).sizable && isNormal)
346                surface.Rectangle(3, 3, size.w - 4, size.h - 4);
347          }
348
349          // Caption
350          if(active)
351             surface.Gradient(gradient, sizeof(gradient) / sizeof(ColorKey), GRADIENT_SMOOTHNESS, GRADIENT_DIRECTION,
352                border, top, size.w - border - 1, top + CAPTION - 2);
353          else
354             surface.Gradient(gradientInactive, sizeof(gradientInactive) / sizeof(ColorKey), 
355             GRADIENT_SMOOTHNESS, GRADIENT_DIRECTION,
356                border, top, size.w - border - 1, top + CAPTION - 2);
357
358          surface.SetForeground(activeBorder);
359          if(state != minimized)
360             surface.HLine(border, size.w-border-1, top + CAPTION-1);
361
362          surface.SetForeground((active ? TEXT_COLOR : TEXT_INACTIVE));
363          surface.TextOpacity(false);
364          surface.TextFont(captionFont);
365          if(name)
366          {
367             int buttonsSize = border +
368                ((hasMaximize || hasMinimize) ? 52 : 18);
369             surface.WriteTextDots(left, border + NAME_OFFSETX, top + NAME_OFFSET, 
370                size.w - (buttonsSize + border + 4), name, strlen(name));
371          }
372       }
373       if(((BorderBits)borderStyle).contour && !((BorderBits)borderStyle).fixed)
374       {
375          surface.SetForeground(black);
376          surface.Rectangle(0, 0, size.w - 1, size.h - 1);
377       }
378
379       if(state != minimized && hasHorzScroll && hasVertScroll)
380       {
381          if(sbh && sbh.visible && sbv && sbv.visible)
382          {
383             surface.SetBackground(activeBorder);
384             surface.Area(
385                clientStart.x + clientSize.w,
386                clientStart.y + clientSize.h,
387                clientStart.x + clientSize.w + SB_WIDTH - 1,
388                clientStart.y + clientSize.h + SB_HEIGHT - 1);
389          }
390       }
391    }
392
393    bool IsMouseMoving(int x, int y, int w, int h)
394    {
395       bool isNormal = (state == normal);
396       bool result = false;
397       if(nativeDecorations && rootWindow == this) return false;
398
399       if(((BorderBits)borderStyle).fixed && (state != maximized || !parent.menuBar))
400       {
401          int corner = 0, border = 0, top = 0;
402          if(((BorderBits)borderStyle).sizable && isNormal)
403          {
404             corner = CORNER;
405             border = BORDER;
406             top    = TOP;
407          }
408          // Special case for having caption on resize bar
409          if(!CAPTION)
410             result = Box { corner, 0, w-corner-1, TOP-1 }.IsPointInside({x,y});
411          else
412             result = Box { border, top, w-border-1, top+CAPTION-1 }.IsPointInside({x, y});
413       }
414       return result;
415    }
416
417    bool IsMouseResizing(int x, int y, int w, int h, bool *resizeX, bool *resizeY, bool *resizeEndX, bool *resizeEndY)
418    {
419       bool result = false;
420
421       *resizeX = *resizeY = *resizeEndX = *resizeEndY = false;
422       if(nativeDecorations && rootWindow == this) return false;
423
424       if(((BorderBits)borderStyle).sizable && (state == normal))
425       {
426          // TopLeft Corner
427          if(Box { 0, 0,CORNER-1, TOP-1 }.IsPointInside({x, y}))
428             result = *resizeX = *resizeY = true;
429          // TopRight Corner
430          if(Box { w-CORNER-1, 0, w-1, TOP-1 }.IsPointInside({x, y}))
431             result = *resizeEndX = *resizeY = true;
432          // BottomLeft Corner
433          if(Box { 0, h-BOTTOM-1, CORNER-1, h-1 }.IsPointInside({x, y}))
434             result = *resizeX = *resizeEndY = true;
435          // BottomRight Corner
436          if(Box { w-CORNER-1, h-BOTTOM-1, w-1, h-1 }.IsPointInside({x, y}))
437             result = *resizeEndX = *resizeEndY = true;
438          // Left Border
439          if(Box { 0,TOP, BORDER, h-BOTTOM-1 }.IsPointInside({x, y}))
440             result = *resizeX = true;
441          // Right Border
442          if(Box { w-BORDER-1, TOP, w-1, h-BOTTOM-1 }.IsPointInside({x, y}))
443             result = *resizeEndX = true;
444          // Top Border
445          if(Box { CORNER, 0, w-CORNER-1, TOP-1 }.IsPointInside({x, y}))
446             result = *resizeY = true;
447          // Bottom Border
448          if(Box { CORNER, h-BOTTOM-1, w-CORNER-1, h-1 }.IsPointInside({x, y}))
449             result = *resizeEndY = true;
450       }
451       return result;
452    }
453
454    void UpdateNonClient()
455    {
456       bool isNormal = (state == normal);
457       int top = 0, border = 0;
458       int insideBorder = 0;
459
460       if(!nativeDecorations || rootWindow != this)
461       {
462          if(state == minimized)
463             top = border = DEAD_BORDER;
464          else if(((BorderBits)borderStyle).sizable)
465          {
466             if(state == maximized && parent.menuBar)
467             {
468                top = 2;
469                border = 2;
470             }
471             else
472             {
473                top = isNormal ? TOP : 0;
474                border = isNormal ? BORDER : 0;
475             }
476          }
477          else if(((BorderBits)borderStyle).fixed)
478          {
479             top = DEAD_BORDER;
480             border = DEAD_BORDER;
481          }
482          else if(((BorderBits)borderStyle).contour)
483          {
484             top = 1;
485             border = 1;
486          }
487          insideBorder = border;
488          if(((BorderBits)borderStyle).deep)
489             insideBorder += 2;
490       }
491
492       if(menuBar)
493       {
494          if(state == minimized)
495             menuBar.visible = false;
496          else
497             menuBar.visible = true;
498          menuBar.Move(clientStart.x, clientStart.y - skinMenuHeight, size.w - insideBorder * 2, skinMenuHeight);
499       }
500       if(statusBar)
501       {
502          if(state == minimized)
503             statusBar.visible = false;
504          else
505          {
506             statusBar.visible = true;
507             if(nativeDecorations && rootWindow == this)
508             {
509                statusBar.anchor = { left = clientStart.x, bottom = (int)(size.h - clientSize.h - clientStart.y - STATUS_HEIGHT ) };
510                statusBar.size.w = size.w - insideBorder * 2;
511             }
512             else
513             {
514                statusBar.anchor = { left = clientStart.x, bottom = border };
515                statusBar.size.w = size.w - insideBorder * 2;
516             }
517          }
518       }
519       if(!nativeDecorations || rootWindow != this)
520       {
521          if(sysButtons[0])
522          {
523             sysButtons[0].anchor = { right = 35 + border, top = top + BUTTON_OFFSET };
524             sysButtons[0].size = { 15, 15 };
525             sysButtons[0].bevel = true;
526             sysButtons[0].bitmap = { skinBitmaps[(state == minimized) ? restore : minimize] };
527             sysButtons[0].visible = true;
528          }
529          if(sysButtons[1])
530          {
531             sysButtons[1].anchor = { right = 20 + border, top = top + BUTTON_OFFSET };
532             sysButtons[1].size = { 15, 15 };
533             sysButtons[1].bevel = true;
534             sysButtons[1].bitmap = { skinBitmaps[(state == maximized) ? restore : maximize] };
535             sysButtons[1].visible = true;
536          }
537          if(sysButtons[2])
538          {
539             sysButtons[2].anchor = { right = 2 + border, top = top + BUTTON_OFFSET };
540             sysButtons[2].size = { 15, 15 };
541             sysButtons[2].bevel = true;
542             sysButtons[2].bitmap = { skinBitmaps[close] };
543             sysButtons[2].visible = true;
544          }
545       }
546    }
547 }