1/*
2* Copyright 2016 Google Inc.
3*
4* Use of this source code is governed by a BSD-style license that can be
5* found in the LICENSE file.
6*/
7
8#include "SkUtils.h"
9#include "Timer.h"
10#include "WindowContextFactory_mac.h"
11#include "Window_mac.h"
12
13namespace sk_app {
14
15SkTDynamicHash<Window_mac, Uint32> Window_mac::gWindowMap;
16
17Window* Window::CreateNativeWindow(void*) {
18    Window_mac* window = new Window_mac();
19    if (!window->initWindow()) {
20        delete window;
21        return nullptr;
22    }
23
24    return window;
25}
26
27bool Window_mac::initWindow() {
28    if (fRequestedDisplayParams.fMSAASampleCount != fMSAASampleCount) {
29        this->closeWindow();
30    }
31    // we already have a window
32    if (fWindow) {
33        return true;
34    }
35
36    constexpr int initialWidth = 1280;
37    constexpr int initialHeight = 960;
38
39    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
40    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
41    SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
42
43    SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8);
44    SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8);
45    SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8);
46    SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
47    SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 0);
48    SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);
49
50    SDL_GL_SetAttribute(SDL_GL_ACCELERATED_VISUAL, 1);
51
52    if (fRequestedDisplayParams.fMSAASampleCount > 1) {
53        SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1);
54        SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, fRequestedDisplayParams.fMSAASampleCount);
55    } else {
56        SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 0);
57    }
58    // TODO: handle other display params
59
60    uint32_t windowFlags = SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE;
61    fWindow = SDL_CreateWindow("SDL Window", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
62                               initialWidth, initialHeight, windowFlags);
63
64    if (!fWindow) {
65        return false;
66    }
67
68    fMSAASampleCount = fRequestedDisplayParams.fMSAASampleCount;
69
70    // add to hashtable of windows
71    fWindowID = SDL_GetWindowID(fWindow);
72    gWindowMap.add(this);
73
74    return true;
75}
76
77void Window_mac::closeWindow() {
78    if (fWindow) {
79        gWindowMap.remove(fWindowID);
80        SDL_DestroyWindow(fWindow);
81        fWindowID = 0;
82        fWindow = nullptr;
83    }
84}
85
86static Window::Key get_key(const SDL_Keysym& keysym) {
87    static const struct {
88        SDL_Keycode fSDLK;
89        Window::Key fKey;
90    } gPair[] = {
91        { SDLK_BACKSPACE, Window::Key::kBack },
92        { SDLK_CLEAR, Window::Key::kBack },
93        { SDLK_RETURN, Window::Key::kOK },
94        { SDLK_UP, Window::Key::kUp },
95        { SDLK_DOWN, Window::Key::kDown },
96        { SDLK_LEFT, Window::Key::kLeft },
97        { SDLK_RIGHT, Window::Key::kRight },
98        { SDLK_TAB, Window::Key::kTab },
99        { SDLK_PAGEUP, Window::Key::kPageUp },
100        { SDLK_PAGEDOWN, Window::Key::kPageDown },
101        { SDLK_HOME, Window::Key::kHome },
102        { SDLK_END, Window::Key::kEnd },
103        { SDLK_DELETE, Window::Key::kDelete },
104        { SDLK_ESCAPE, Window::Key::kEscape },
105        { SDLK_LSHIFT, Window::Key::kShift },
106        { SDLK_RSHIFT, Window::Key::kShift },
107        { SDLK_LCTRL, Window::Key::kCtrl },
108        { SDLK_RCTRL, Window::Key::kCtrl },
109        { SDLK_LALT, Window::Key::kOption },
110        { SDLK_LALT, Window::Key::kOption },
111        { 'A', Window::Key::kA },
112        { 'C', Window::Key::kC },
113        { 'V', Window::Key::kV },
114        { 'X', Window::Key::kX },
115        { 'Y', Window::Key::kY },
116        { 'Z', Window::Key::kZ },
117    };
118    for (size_t i = 0; i < SK_ARRAY_COUNT(gPair); i++) {
119        if (gPair[i].fSDLK == keysym.sym) {
120            return gPair[i].fKey;
121        }
122    }
123    return Window::Key::kNONE;
124}
125
126static uint32_t get_modifiers(const SDL_Event& event) {
127    static const struct {
128        unsigned    fSDLMask;
129        unsigned    fSkMask;
130    } gModifiers[] = {
131        { KMOD_SHIFT, Window::kShift_ModifierKey },
132        { KMOD_CTRL,  Window::kControl_ModifierKey },
133        { KMOD_ALT,   Window::kOption_ModifierKey },
134    };
135
136    auto modifiers = 0;
137
138    switch (event.type) {
139        case SDL_KEYDOWN:
140            // fall through
141        case SDL_KEYUP: {
142            for (size_t i = 0; i < SK_ARRAY_COUNT(gModifiers); ++i) {
143                if (event.key.keysym.mod & gModifiers[i].fSDLMask) {
144                    modifiers |= gModifiers[i].fSkMask;
145                }
146            }
147            if (0 == event.key.repeat) {
148                modifiers |= Window::kFirstPress_ModifierKey;
149            }
150            break;
151        }
152
153        default: {
154            SDL_Keymod mod = SDL_GetModState();
155            for (size_t i = 0; i < SK_ARRAY_COUNT(gModifiers); ++i) {
156                if (mod & gModifiers[i].fSDLMask) {
157                    modifiers |= gModifiers[i].fSkMask;
158                }
159            }
160            break;
161        }
162    }
163    return modifiers;
164}
165
166bool Window_mac::HandleWindowEvent(const SDL_Event& event) {
167    Window_mac* win = gWindowMap.find(event.window.windowID);
168    if (win && win->handleEvent(event)) {
169        return true;
170    }
171
172    return false;
173}
174
175bool Window_mac::handleEvent(const SDL_Event& event) {
176    switch (event.type) {
177        case SDL_WINDOWEVENT:
178            if (SDL_WINDOWEVENT_EXPOSED == event.window.event) {
179                this->onPaint();
180            } else if (SDL_WINDOWEVENT_RESIZED == event.window.event) {
181                this->onResize(event.window.data1, event.window.data2);
182            }
183            break;
184
185        case SDL_MOUSEBUTTONDOWN:
186            if (event.button.button == SDL_BUTTON_LEFT) {
187                this->onMouse(event.button.x, event.button.y,
188                              Window::kDown_InputState, get_modifiers(event));
189            }
190            break;
191
192        case SDL_MOUSEBUTTONUP:
193            if (event.button.button == SDL_BUTTON_LEFT) {
194                this->onMouse(event.button.x, event.button.y,
195                              Window::kUp_InputState, get_modifiers(event));
196            }
197            break;
198
199        case SDL_MOUSEMOTION:
200            this->onMouse(event.motion.x, event.motion.y,
201                          Window::kMove_InputState, get_modifiers(event));
202            break;
203
204        case SDL_MOUSEWHEEL:
205            this->onMouseWheel(event.wheel.y, get_modifiers(event));
206            break;
207
208        case SDL_KEYDOWN: {
209            Window::Key key = get_key(event.key.keysym);
210            if (key != Window::Key::kNONE) {
211                if (!this->onKey(key, Window::kDown_InputState, get_modifiers(event))) {
212                    if (event.key.keysym.sym == SDLK_ESCAPE) {
213                        return true;
214                    }
215                }
216            }
217        } break;
218
219        case SDL_KEYUP: {
220            Window::Key key = get_key(event.key.keysym);
221            if (key != Window::Key::kNONE) {
222                (void) this->onKey(key, Window::kUp_InputState,
223                                   get_modifiers(event));
224            }
225        } break;
226
227        case SDL_TEXTINPUT: {
228            const char* textIter = &event.text.text[0];
229            while (SkUnichar c = SkUTF8_NextUnichar(&textIter)) {
230                (void) this->onChar(c, get_modifiers(event));
231            }
232        } break;
233
234        default:
235            break;
236    }
237
238    return false;
239}
240
241void Window_mac::setTitle(const char* title) {
242    SDL_SetWindowTitle(fWindow, title);
243}
244
245void Window_mac::show() {
246    SDL_ShowWindow(fWindow);
247}
248
249bool Window_mac::attach(BackendType attachType) {
250    this->initWindow();
251
252    window_context_factory::MacWindowInfo info;
253    info.fWindow = fWindow;
254    switch (attachType) {
255        case kRaster_BackendType:
256            fWindowContext = NewRasterForMac(info, fRequestedDisplayParams);
257            break;
258
259        case kNativeGL_BackendType:
260        default:
261            fWindowContext = NewGLForMac(info, fRequestedDisplayParams);
262            break;
263    }
264    this->onBackendCreated();
265
266    return (SkToBool(fWindowContext));
267}
268
269void Window_mac::onInval() {
270    SDL_Event sdlevent;
271    sdlevent.type = SDL_WINDOWEVENT;
272    sdlevent.window.windowID = fWindowID;
273    sdlevent.window.event = SDL_WINDOWEVENT_EXPOSED;
274    SDL_PushEvent(&sdlevent);
275}
276
277}   // namespace sk_app
278