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