Merge branch 'master' into jgrpp
# Conflicts: # src/company_cmd.cpp # src/core/geometry_func.cpp # src/date.cpp # src/genworld_gui.cpp # src/gfx.cpp # src/object_gui.cpp # src/openttd.cpp # src/settings_type.h # src/video/allegro_v.cpp # src/video/dedicated_v.cpp # src/video/null_v.cpp # src/video/sdl2_v.cpp # src/video/sdl_v.cpp # src/video/win32_v.cpp
This commit is contained in:
@@ -23,7 +23,9 @@
|
||||
#include "../core/random_func.hpp"
|
||||
#include "../core/math_func.hpp"
|
||||
#include "../framerate_type.h"
|
||||
#include "../progress.h"
|
||||
#include "../thread.h"
|
||||
#include "../window_func.h"
|
||||
#include "allegro_v.h"
|
||||
#include <allegro.h>
|
||||
|
||||
@@ -235,7 +237,7 @@ bool VideoDriver_Allegro::ClaimMousePointer()
|
||||
return true;
|
||||
}
|
||||
|
||||
struct VkMapping {
|
||||
struct AllegroVkMapping {
|
||||
uint16 vk_from;
|
||||
byte vk_count;
|
||||
byte map_to;
|
||||
@@ -244,7 +246,7 @@ struct VkMapping {
|
||||
#define AS(x, z) {x, 0, z}
|
||||
#define AM(x, y, z, w) {x, y - x, z}
|
||||
|
||||
static const VkMapping _vk_mapping[] = {
|
||||
static const AllegroVkMapping _vk_mapping[] = {
|
||||
/* Pageup stuff + up/down */
|
||||
AM(KEY_PGUP, KEY_PGDN, WKC_PAGEUP, WKC_PAGEDOWN),
|
||||
AS(KEY_UP, WKC_UP),
|
||||
@@ -302,7 +304,7 @@ static uint32 ConvertAllegroKeyIntoMy(WChar *character)
|
||||
int scancode;
|
||||
int unicode = ureadkey(&scancode);
|
||||
|
||||
const VkMapping *map;
|
||||
const AllegroVkMapping *map;
|
||||
uint key = 0;
|
||||
|
||||
for (map = _vk_mapping; map != endof(_vk_mapping); ++map) {
|
||||
@@ -445,34 +447,16 @@ void VideoDriver_Allegro::Stop()
|
||||
if (--_allegro_instance_count == 0) allegro_exit();
|
||||
}
|
||||
|
||||
#if defined(UNIX) || defined(__OS2__)
|
||||
# include <sys/time.h> /* gettimeofday */
|
||||
|
||||
static uint32 GetTime()
|
||||
{
|
||||
struct timeval tim;
|
||||
|
||||
gettimeofday(&tim, nullptr);
|
||||
return tim.tv_usec / 1000 + tim.tv_sec * 1000;
|
||||
}
|
||||
#else
|
||||
static uint32 GetTime()
|
||||
{
|
||||
return GetTickCount();
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
void VideoDriver_Allegro::MainLoop()
|
||||
{
|
||||
uint32 cur_ticks = GetTime();
|
||||
uint32 last_cur_ticks = cur_ticks;
|
||||
uint32 next_tick = cur_ticks + MILLISECONDS_PER_TICK;
|
||||
auto cur_ticks = std::chrono::steady_clock::now();
|
||||
auto last_realtime_tick = cur_ticks;
|
||||
auto next_game_tick = cur_ticks;
|
||||
auto next_draw_tick = cur_ticks;
|
||||
|
||||
CheckPaletteAnim();
|
||||
|
||||
for (;;) {
|
||||
uint32 prev_cur_ticks = cur_ticks; // to check for wrapping
|
||||
InteractiveRandom(); // randomness
|
||||
|
||||
PollEvent();
|
||||
@@ -491,11 +475,33 @@ void VideoDriver_Allegro::MainLoop()
|
||||
_fast_forward = 0;
|
||||
}
|
||||
|
||||
cur_ticks = GetTime();
|
||||
if (cur_ticks >= next_tick || (_fast_forward && !_pause_mode) || cur_ticks < prev_cur_ticks) {
|
||||
_realtime_tick += cur_ticks - last_cur_ticks;
|
||||
last_cur_ticks = cur_ticks;
|
||||
next_tick = cur_ticks + MILLISECONDS_PER_TICK;
|
||||
cur_ticks = std::chrono::steady_clock::now();
|
||||
|
||||
/* If more than a millisecond has passed, increase the _realtime_tick. */
|
||||
if (cur_ticks - last_realtime_tick > std::chrono::milliseconds(1)) {
|
||||
auto delta = std::chrono::duration_cast<std::chrono::milliseconds>(cur_ticks - last_realtime_tick);
|
||||
IncreaseRealtimeTick(delta.count());
|
||||
last_realtime_tick += delta;
|
||||
}
|
||||
|
||||
if (cur_ticks >= next_game_tick || (_fast_forward && !_pause_mode)) {
|
||||
if (_fast_forward && !_pause_mode) {
|
||||
next_game_tick = cur_ticks + this->GetGameInterval();
|
||||
} else {
|
||||
next_game_tick += this->GetGameInterval();
|
||||
/* Avoid next_game_tick getting behind more and more if it cannot keep up. */
|
||||
if (next_game_tick < cur_ticks - ALLOWED_DRIFT * this->GetGameInterval()) next_game_tick = cur_ticks;
|
||||
}
|
||||
|
||||
GameLoop();
|
||||
GameLoopPaletteAnimations();
|
||||
}
|
||||
|
||||
/* Prevent drawing when switching mode, as windows can be removed when they should still appear. */
|
||||
if (cur_ticks >= next_draw_tick && (_switch_mode == SM_NONE || HasModalProgress())) {
|
||||
next_draw_tick += this->GetDrawInterval();
|
||||
/* Avoid next_draw_tick getting behind more and more if it cannot keep up. */
|
||||
if (next_draw_tick < cur_ticks - ALLOWED_DRIFT * this->GetDrawInterval()) next_draw_tick = cur_ticks;
|
||||
|
||||
bool old_ctrl_pressed = _ctrl_pressed;
|
||||
bool old_shift_pressed = _shift_pressed;
|
||||
@@ -513,17 +519,22 @@ void VideoDriver_Allegro::MainLoop()
|
||||
if (old_ctrl_pressed != _ctrl_pressed) HandleCtrlChanged();
|
||||
if (old_shift_pressed != _shift_pressed) HandleShiftChanged();
|
||||
|
||||
GameLoop();
|
||||
GameLoopPaletteAnimations();
|
||||
|
||||
InputLoop();
|
||||
UpdateWindows();
|
||||
CheckPaletteAnim();
|
||||
|
||||
DrawSurfaceToScreen();
|
||||
} else {
|
||||
CSleep(1);
|
||||
NetworkDrawChatMessage();
|
||||
DrawMouseCursor();
|
||||
DrawSurfaceToScreen();
|
||||
}
|
||||
|
||||
/* If we are not in fast-forward, create some time between calls to ease up CPU usage. */
|
||||
if (!_fast_forward || _pause_mode) {
|
||||
/* See how much time there is till we have to process the next event, and try to hit that as close as possible. */
|
||||
auto next_tick = std::min(next_draw_tick, next_game_tick);
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
|
||||
if (next_tick > now) {
|
||||
std::this_thread::sleep_for(next_tick - now);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,5 @@ add_files(
|
||||
cocoa_v.mm
|
||||
cocoa_wnd.h
|
||||
cocoa_wnd.mm
|
||||
event.mm
|
||||
CONDITION APPLE
|
||||
)
|
||||
|
||||
@@ -131,4 +131,126 @@
|
||||
#define QZ_IBOOK_DOWN 0x3D
|
||||
#define QZ_IBOOK_UP 0x3E
|
||||
|
||||
|
||||
struct CocoaVkMapping {
|
||||
unsigned short vk_from;
|
||||
byte map_to;
|
||||
};
|
||||
|
||||
#define AS(x, z) {x, z}
|
||||
|
||||
static const CocoaVkMapping _vk_mapping[] = {
|
||||
AS(QZ_BACKQUOTE, WKC_BACKQUOTE), // key left of '1'
|
||||
AS(QZ_BACKQUOTE2, WKC_BACKQUOTE), // some keyboards have it on another scancode
|
||||
|
||||
/* Pageup stuff + up/down */
|
||||
AS(QZ_PAGEUP, WKC_PAGEUP),
|
||||
AS(QZ_PAGEDOWN, WKC_PAGEDOWN),
|
||||
|
||||
AS(QZ_UP, WKC_UP),
|
||||
AS(QZ_DOWN, WKC_DOWN),
|
||||
AS(QZ_LEFT, WKC_LEFT),
|
||||
AS(QZ_RIGHT, WKC_RIGHT),
|
||||
|
||||
AS(QZ_HOME, WKC_HOME),
|
||||
AS(QZ_END, WKC_END),
|
||||
|
||||
AS(QZ_INSERT, WKC_INSERT),
|
||||
AS(QZ_DELETE, WKC_DELETE),
|
||||
|
||||
/* Letters. QZ_[a-z] is not in numerical order so we can't use AM(...) */
|
||||
AS(QZ_a, 'A'),
|
||||
AS(QZ_b, 'B'),
|
||||
AS(QZ_c, 'C'),
|
||||
AS(QZ_d, 'D'),
|
||||
AS(QZ_e, 'E'),
|
||||
AS(QZ_f, 'F'),
|
||||
AS(QZ_g, 'G'),
|
||||
AS(QZ_h, 'H'),
|
||||
AS(QZ_i, 'I'),
|
||||
AS(QZ_j, 'J'),
|
||||
AS(QZ_k, 'K'),
|
||||
AS(QZ_l, 'L'),
|
||||
AS(QZ_m, 'M'),
|
||||
AS(QZ_n, 'N'),
|
||||
AS(QZ_o, 'O'),
|
||||
AS(QZ_p, 'P'),
|
||||
AS(QZ_q, 'Q'),
|
||||
AS(QZ_r, 'R'),
|
||||
AS(QZ_s, 'S'),
|
||||
AS(QZ_t, 'T'),
|
||||
AS(QZ_u, 'U'),
|
||||
AS(QZ_v, 'V'),
|
||||
AS(QZ_w, 'W'),
|
||||
AS(QZ_x, 'X'),
|
||||
AS(QZ_y, 'Y'),
|
||||
AS(QZ_z, 'Z'),
|
||||
/* Same thing for digits */
|
||||
AS(QZ_0, '0'),
|
||||
AS(QZ_1, '1'),
|
||||
AS(QZ_2, '2'),
|
||||
AS(QZ_3, '3'),
|
||||
AS(QZ_4, '4'),
|
||||
AS(QZ_5, '5'),
|
||||
AS(QZ_6, '6'),
|
||||
AS(QZ_7, '7'),
|
||||
AS(QZ_8, '8'),
|
||||
AS(QZ_9, '9'),
|
||||
|
||||
AS(QZ_ESCAPE, WKC_ESC),
|
||||
AS(QZ_PAUSE, WKC_PAUSE),
|
||||
AS(QZ_BACKSPACE, WKC_BACKSPACE),
|
||||
|
||||
AS(QZ_SPACE, WKC_SPACE),
|
||||
AS(QZ_RETURN, WKC_RETURN),
|
||||
AS(QZ_TAB, WKC_TAB),
|
||||
|
||||
/* Function keys */
|
||||
AS(QZ_F1, WKC_F1),
|
||||
AS(QZ_F2, WKC_F2),
|
||||
AS(QZ_F3, WKC_F3),
|
||||
AS(QZ_F4, WKC_F4),
|
||||
AS(QZ_F5, WKC_F5),
|
||||
AS(QZ_F6, WKC_F6),
|
||||
AS(QZ_F7, WKC_F7),
|
||||
AS(QZ_F8, WKC_F8),
|
||||
AS(QZ_F9, WKC_F9),
|
||||
AS(QZ_F10, WKC_F10),
|
||||
AS(QZ_F11, WKC_F11),
|
||||
AS(QZ_F12, WKC_F12),
|
||||
|
||||
/* Numeric part */
|
||||
AS(QZ_KP0, '0'),
|
||||
AS(QZ_KP1, '1'),
|
||||
AS(QZ_KP2, '2'),
|
||||
AS(QZ_KP3, '3'),
|
||||
AS(QZ_KP4, '4'),
|
||||
AS(QZ_KP5, '5'),
|
||||
AS(QZ_KP6, '6'),
|
||||
AS(QZ_KP7, '7'),
|
||||
AS(QZ_KP8, '8'),
|
||||
AS(QZ_KP9, '9'),
|
||||
AS(QZ_KP_DIVIDE, WKC_NUM_DIV),
|
||||
AS(QZ_KP_MULTIPLY, WKC_NUM_MUL),
|
||||
AS(QZ_KP_MINUS, WKC_NUM_MINUS),
|
||||
AS(QZ_KP_PLUS, WKC_NUM_PLUS),
|
||||
AS(QZ_KP_ENTER, WKC_NUM_ENTER),
|
||||
AS(QZ_KP_PERIOD, WKC_NUM_DECIMAL),
|
||||
|
||||
/* Other non-letter keys */
|
||||
AS(QZ_SLASH, WKC_SLASH),
|
||||
AS(QZ_SEMICOLON, WKC_SEMICOLON),
|
||||
AS(QZ_EQUALS, WKC_EQUALS),
|
||||
AS(QZ_LEFTBRACKET, WKC_L_BRACKET),
|
||||
AS(QZ_BACKSLASH, WKC_BACKSLASH),
|
||||
AS(QZ_RIGHTBRACKET, WKC_R_BRACKET),
|
||||
|
||||
AS(QZ_QUOTE, WKC_SINGLEQUOTE),
|
||||
AS(QZ_COMMA, WKC_COMMA),
|
||||
AS(QZ_MINUS, WKC_MINUS),
|
||||
AS(QZ_PERIOD, WKC_PERIOD)
|
||||
};
|
||||
|
||||
#undef AS
|
||||
|
||||
#endif
|
||||
|
||||
@@ -16,86 +16,13 @@
|
||||
|
||||
extern bool _cocoa_video_started;
|
||||
|
||||
@class OTTD_CocoaWindowDelegate;
|
||||
@class OTTD_CocoaWindow;
|
||||
@class OTTD_CocoaView;
|
||||
|
||||
class VideoDriver_Cocoa : public VideoDriver {
|
||||
private:
|
||||
Dimension orig_res; ///< Saved window size for non-fullscreen mode.
|
||||
|
||||
public:
|
||||
const char *Start(const StringList ¶m) override;
|
||||
|
||||
/** Stop the video driver */
|
||||
void Stop() override;
|
||||
|
||||
/** Mark dirty a screen region
|
||||
* @param left x-coordinate of left border
|
||||
* @param top y-coordinate of top border
|
||||
* @param width width or dirty rectangle
|
||||
* @param height height of dirty rectangle
|
||||
*/
|
||||
void MakeDirty(int left, int top, int width, int height) override;
|
||||
|
||||
/** Programme main loop */
|
||||
void MainLoop() override;
|
||||
|
||||
/** Change window resolution
|
||||
* @param w New window width
|
||||
* @param h New window height
|
||||
* @return Whether change was successful
|
||||
*/
|
||||
bool ChangeResolution(int w, int h) override;
|
||||
|
||||
/** Set a new window mode
|
||||
* @param fullscreen Whether to set fullscreen mode or not
|
||||
* @return Whether changing the screen mode was successful
|
||||
*/
|
||||
bool ToggleFullscreen(bool fullscreen) override;
|
||||
|
||||
/** Callback invoked after the blitter was changed.
|
||||
* @return True if no error.
|
||||
*/
|
||||
bool AfterBlitterChange() override;
|
||||
|
||||
/**
|
||||
* An edit box lost the input focus. Abort character compositing if necessary.
|
||||
*/
|
||||
void EditBoxLostFocus() override;
|
||||
|
||||
/** Return driver name
|
||||
* @return driver name
|
||||
*/
|
||||
const char *GetName() const override { return "cocoa"; }
|
||||
|
||||
/* --- The following methods should be private, but can't be due to Obj-C limitations. --- */
|
||||
|
||||
/** Main game loop. */
|
||||
void GameLoop(); // In event.mm.
|
||||
|
||||
protected:
|
||||
Dimension GetScreenSize() const override;
|
||||
|
||||
private:
|
||||
friend class WindowQuartzSubdriver;
|
||||
|
||||
void GameSizeChanged();
|
||||
};
|
||||
|
||||
class FVideoDriver_Cocoa : public DriverFactoryBase {
|
||||
public:
|
||||
FVideoDriver_Cocoa() : DriverFactoryBase(Driver::DT_VIDEO, 10, "cocoa", "Cocoa Video Driver") {}
|
||||
Driver *CreateInstance() const override { return new VideoDriver_Cocoa(); }
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Generic display driver for cocoa
|
||||
* On grounds to not duplicate some code, it contains a few variables
|
||||
* which are not used by all device drivers.
|
||||
*/
|
||||
class CocoaSubdriver {
|
||||
public:
|
||||
int device_width; ///< Width of device in pixel
|
||||
int device_height; ///< Height of device in pixel
|
||||
int device_depth; ///< Colour depth of device in bit
|
||||
Dimension orig_res; ///< Saved window size for non-fullscreen mode.
|
||||
|
||||
int window_width; ///< Current window width in pixel
|
||||
int window_height; ///< Current window height in pixel
|
||||
@@ -104,110 +31,71 @@ public:
|
||||
int buffer_depth; ///< Colour depth of used frame buffer
|
||||
void *pixel_buffer; ///< used for direct pixel access
|
||||
void *window_buffer; ///< Colour translation from palette to screen
|
||||
CGColorSpaceRef color_space; //< Window color space
|
||||
id window; ///< Pointer to window object
|
||||
|
||||
# define MAX_DIRTY_RECTS 100
|
||||
static const int MAX_DIRTY_RECTS = 100;
|
||||
|
||||
Rect dirty_rects[MAX_DIRTY_RECTS]; ///< dirty rectangles
|
||||
int num_dirty_rects; ///< Number of dirty rectangles
|
||||
uint num_dirty_rects; ///< Number of dirty rectangles
|
||||
uint32 palette[256]; ///< Colour Palette
|
||||
|
||||
bool active; ///< Whether the window is visible
|
||||
bool setup;
|
||||
public:
|
||||
bool setup; ///< Window is currently being created.
|
||||
|
||||
id cocoaview; ///< Pointer to view object
|
||||
OTTD_CocoaWindow *window; ///< Pointer to window object
|
||||
OTTD_CocoaView *cocoaview; ///< Pointer to view object
|
||||
CGColorSpaceRef color_space; ///< Window color space
|
||||
CGContextRef cgcontext; ///< Context reference for Quartz subdriver
|
||||
|
||||
/* Separate driver vars for Quarz
|
||||
* Needed here in order to avoid much code duplication */
|
||||
CGContextRef cgcontext; ///< Context reference for Quartz subdriver
|
||||
OTTD_CocoaWindowDelegate *delegate; //!< Window delegate object
|
||||
|
||||
/* Driver methods */
|
||||
/** Initialize driver */
|
||||
virtual ~CocoaSubdriver() {}
|
||||
public:
|
||||
VideoDriver_Cocoa();
|
||||
|
||||
/** Draw window
|
||||
* @param force_update Whether to redraw unconditionally
|
||||
*/
|
||||
virtual void Draw(bool force_update = false) = 0;
|
||||
const char *Start(const StringList ¶m) override;
|
||||
void Stop() override;
|
||||
void MainLoop() override;
|
||||
|
||||
/** Mark dirty a screen region
|
||||
* @param left x-coordinate of left border
|
||||
* @param top y-coordinate of top border
|
||||
* @param width width or dirty rectangle
|
||||
* @param height height of dirty rectangle
|
||||
*/
|
||||
virtual void MakeDirty(int left, int top, int width, int height) = 0;
|
||||
void MakeDirty(int left, int top, int width, int height) override;
|
||||
bool AfterBlitterChange() override;
|
||||
|
||||
/** Update the palette */
|
||||
virtual void UpdatePalette(uint first_color, uint num_colors) = 0;
|
||||
bool ChangeResolution(int w, int h) override;
|
||||
bool ToggleFullscreen(bool fullscreen) override;
|
||||
|
||||
virtual uint ListModes(OTTD_Point *modes, uint max_modes) = 0;
|
||||
void EditBoxLostFocus() override;
|
||||
|
||||
/** Change window resolution
|
||||
* @param w New window width
|
||||
* @param h New window height
|
||||
* @return Whether change was successful
|
||||
*/
|
||||
virtual bool ChangeResolution(int w, int h, int bpp) = 0;
|
||||
const char *GetName() const override { return "cocoa"; }
|
||||
|
||||
/** Are we in fullscreen mode
|
||||
* @return whether fullscreen mode is currently used
|
||||
*/
|
||||
virtual bool IsFullscreen() = 0;
|
||||
/* --- The following methods should be private, but can't be due to Obj-C limitations. --- */
|
||||
|
||||
/** Toggle between fullscreen and windowed mode
|
||||
* @return whether switch was successful
|
||||
*/
|
||||
virtual bool ToggleFullscreen(bool fullscreen) { return false; };
|
||||
void GameLoop();
|
||||
|
||||
/** Return the width of the current view
|
||||
* @return width of the current view
|
||||
*/
|
||||
virtual int GetWidth() = 0;
|
||||
void AllocateBackingStore();
|
||||
|
||||
/** Return the height of the current view
|
||||
* @return height of the current view
|
||||
*/
|
||||
virtual int GetHeight() = 0;
|
||||
protected:
|
||||
Dimension GetScreenSize() const override;
|
||||
float GetDPIScale() override;
|
||||
|
||||
/** Return the current pixel buffer
|
||||
* @return pixelbuffer
|
||||
*/
|
||||
virtual void *GetPixelBuffer() = 0;
|
||||
private:
|
||||
bool PollEvent();
|
||||
|
||||
/** Convert local coordinate to window server (CoreGraphics) coordinate
|
||||
* @param p local coordinates
|
||||
* @return window driver coordinates
|
||||
*/
|
||||
virtual CGPoint PrivateLocalToCG(NSPoint *p) = 0;
|
||||
bool IsFullscreen();
|
||||
void GameSizeChanged();
|
||||
|
||||
/** Return the mouse location
|
||||
* @param event UI event
|
||||
* @return mouse location as NSPoint
|
||||
*/
|
||||
virtual NSPoint GetMouseLocation(NSEvent *event) = 0;
|
||||
void UpdateVideoModes();
|
||||
|
||||
/** Return whether the mouse is within our view
|
||||
* @param pt Mouse coordinates
|
||||
* @return Whether mouse coordinates are within view
|
||||
*/
|
||||
virtual bool MouseIsInsideView(NSPoint *pt) = 0;
|
||||
bool MakeWindow(int width, int height);
|
||||
|
||||
/** Return whether the window is active (visible)
|
||||
* @return whether the window is visible or not
|
||||
*/
|
||||
virtual bool IsActive() = 0;
|
||||
void UpdatePalette(uint first_color, uint num_colors);
|
||||
void CheckPaletteAnim();
|
||||
|
||||
/** Whether the window was successfully resized
|
||||
* @return whether the window was successfully resized
|
||||
*/
|
||||
virtual bool WindowResized() { return false; };
|
||||
void Draw(bool force_update = false);
|
||||
void BlitIndexedToView32(int left, int top, int right, int bottom);
|
||||
};
|
||||
|
||||
extern CocoaSubdriver *_cocoa_subdriver;
|
||||
|
||||
CocoaSubdriver *QZ_CreateWindowQuartzSubdriver(int width, int height, int bpp);
|
||||
|
||||
uint QZ_ListModes(OTTD_Point *modes, uint max_modes, CGDirectDisplayID display_id, int display_depth);
|
||||
class FVideoDriver_Cocoa : public DriverFactoryBase {
|
||||
public:
|
||||
FVideoDriver_Cocoa() : DriverFactoryBase(Driver::DT_VIDEO, 10, "cocoa", "Cocoa Video Driver") {}
|
||||
Driver *CreateInstance() const override { return new VideoDriver_Cocoa(); }
|
||||
};
|
||||
|
||||
#endif /* VIDEO_COCOA_H */
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -12,7 +12,14 @@
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
class CocoaSubdriver;
|
||||
class VideoDriver_Cocoa;
|
||||
|
||||
/* Right Mouse Button Emulation enum */
|
||||
enum RightMouseButtonEmulationState {
|
||||
RMBE_COMMAND = 0,
|
||||
RMBE_CONTROL = 1,
|
||||
RMBE_OFF = 2,
|
||||
};
|
||||
|
||||
extern NSString *OTTDMainLaunchGameEngine;
|
||||
|
||||
@@ -22,51 +29,28 @@ extern NSString *OTTDMainLaunchGameEngine;
|
||||
@end
|
||||
|
||||
/** Subclass of NSWindow to cater our special needs */
|
||||
@interface OTTD_CocoaWindow : NSWindow {
|
||||
CocoaSubdriver *driver;
|
||||
}
|
||||
@interface OTTD_CocoaWindow : NSWindow
|
||||
- (instancetype)initWithContentRect:(NSRect)contentRect styleMask:(NSUInteger)styleMask backing:(NSBackingStoreType)backingType defer:(BOOL)flag driver:(VideoDriver_Cocoa *)drv;
|
||||
|
||||
- (void)setDriver:(CocoaSubdriver*)drv;
|
||||
|
||||
- (void)miniaturize:(id)sender;
|
||||
- (void)display;
|
||||
- (void)setFrame:(NSRect)frameRect display:(BOOL)flag;
|
||||
- (void)appDidHide:(NSNotification*)note;
|
||||
- (void)appDidUnhide:(NSNotification*)note;
|
||||
- (id)initWithContentRect:(NSRect)contentRect styleMask:(NSUInteger)styleMask backing:(NSBackingStoreType)backingType defer:(BOOL)flag;
|
||||
@end
|
||||
|
||||
/** Subclass of NSView to fix Quartz rendering and mouse awareness */
|
||||
/** Subclass of NSView to support mouse awareness and text input. */
|
||||
@interface OTTD_CocoaView : NSView <NSTextInputClient>
|
||||
{
|
||||
CocoaSubdriver *driver;
|
||||
NSTrackingRectTag trackingtag;
|
||||
}
|
||||
- (void)setDriver:(CocoaSubdriver*)drv;
|
||||
- (void)drawRect:(NSRect)rect;
|
||||
- (BOOL)isOpaque;
|
||||
- (BOOL)acceptsFirstResponder;
|
||||
- (BOOL)becomeFirstResponder;
|
||||
- (void)setTrackingRect;
|
||||
- (void)clearTrackingRect;
|
||||
- (void)resetCursorRects;
|
||||
- (void)viewWillMoveToWindow:(NSWindow *)win;
|
||||
- (void)viewDidMoveToWindow;
|
||||
- (void)mouseEntered:(NSEvent *)theEvent;
|
||||
- (void)mouseExited:(NSEvent *)theEvent;
|
||||
- (NSRect)getRealRect:(NSRect)rect;
|
||||
- (NSRect)getVirtualRect:(NSRect)rect;
|
||||
- (CGFloat)getContentsScale;
|
||||
- (NSPoint)mousePositionFromEvent:(NSEvent *)e;
|
||||
@end
|
||||
|
||||
/** Delegate for our NSWindow to send ask for quit on close */
|
||||
@interface OTTD_CocoaWindowDelegate : NSObject <NSWindowDelegate>
|
||||
{
|
||||
CocoaSubdriver *driver;
|
||||
}
|
||||
|
||||
- (void)setDriver:(CocoaSubdriver*)drv;
|
||||
- (instancetype)initWithDriver:(VideoDriver_Cocoa *)drv;
|
||||
|
||||
- (BOOL)windowShouldClose:(id)sender;
|
||||
- (void)windowDidEnterFullScreen:(NSNotification *)aNotification;
|
||||
- (void)windowDidChangeScreenProfile:(NSNotification *)aNotification;
|
||||
- (void)windowDidChangeBackingProperties:(NSNotification *)notification;
|
||||
- (NSApplicationPresentationOptions)window:(NSWindow *)window willUseFullScreenPresentationOptions:(NSApplicationPresentationOptions)proposedOptions;
|
||||
@end
|
||||
|
||||
|
||||
@@ -24,14 +24,20 @@
|
||||
|
||||
#include "../../openttd.h"
|
||||
#include "../../debug.h"
|
||||
#include "../../rev.h"
|
||||
#include "cocoa_v.h"
|
||||
#include "cocoa_wnd.h"
|
||||
#include "../../settings_type.h"
|
||||
#include "../../string_func.h"
|
||||
#include "../../gfx_func.h"
|
||||
#include "../../window_func.h"
|
||||
#include "../../window_gui.h"
|
||||
|
||||
|
||||
/* Table data for key mapping. */
|
||||
#include "cocoa_keys.h"
|
||||
|
||||
|
||||
/**
|
||||
* Important notice regarding all modifications!!!!!!!
|
||||
* There are certain limitations because the file is objective C++.
|
||||
@@ -40,14 +46,83 @@
|
||||
* Read http://developer.apple.com/releasenotes/Cocoa/Objective-C++.html for more information.
|
||||
*/
|
||||
|
||||
bool _allow_hidpi_window = true; // Referenced from table/misc_settings.ini
|
||||
|
||||
@interface OTTDMain : NSObject <NSApplicationDelegate>
|
||||
@end
|
||||
|
||||
NSString *OTTDMainLaunchGameEngine = @"ottdmain_launch_game_engine";
|
||||
|
||||
bool _tab_is_down;
|
||||
|
||||
static bool _cocoa_video_dialog = false;
|
||||
static OTTDMain *_ottd_main;
|
||||
|
||||
|
||||
/**
|
||||
* Count the number of UTF-16 code points in a range of an UTF-8 string.
|
||||
* @param from Start of the range.
|
||||
* @param to End of the range.
|
||||
* @return Number of UTF-16 code points in the range.
|
||||
*/
|
||||
static NSUInteger CountUtf16Units(const char *from, const char *to)
|
||||
{
|
||||
NSUInteger i = 0;
|
||||
|
||||
while (from < to) {
|
||||
WChar c;
|
||||
size_t len = Utf8Decode(&c, from);
|
||||
i += len < 4 ? 1 : 2; // Watch for surrogate pairs.
|
||||
from += len;
|
||||
}
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
/**
|
||||
* Advance an UTF-8 string by a number of equivalent UTF-16 code points.
|
||||
* @param str UTF-8 string.
|
||||
* @param count Number of UTF-16 code points to advance the string by.
|
||||
* @return Advanced string pointer.
|
||||
*/
|
||||
static const char *Utf8AdvanceByUtf16Units(const char *str, NSUInteger count)
|
||||
{
|
||||
for (NSUInteger i = 0; i < count && *str != '\0'; ) {
|
||||
WChar c;
|
||||
size_t len = Utf8Decode(&c, str);
|
||||
i += len < 4 ? 1 : 2; // Watch for surrogates.
|
||||
str += len;
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a NSString to an UTF-32 encoded string.
|
||||
* @param s String to convert.
|
||||
* @return Vector of UTF-32 characters.
|
||||
*/
|
||||
static std::vector<WChar> NSStringToUTF32(NSString *s)
|
||||
{
|
||||
std::vector<WChar> unicode_str;
|
||||
|
||||
unichar lead = 0;
|
||||
for (NSUInteger i = 0; i < s.length; i++) {
|
||||
unichar c = [ s characterAtIndex:i ];
|
||||
if (Utf16IsLeadSurrogate(c)) {
|
||||
lead = c;
|
||||
continue;
|
||||
} else if (Utf16IsTrailSurrogate(c)) {
|
||||
if (lead != 0) unicode_str.push_back(Utf16DecodeSurrogate(lead, c));
|
||||
} else {
|
||||
unicode_str.push_back(c);
|
||||
}
|
||||
}
|
||||
|
||||
return unicode_str;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The main class of the application, the application's delegate.
|
||||
*/
|
||||
@@ -69,11 +144,15 @@ static OTTDMain *_ottd_main;
|
||||
*/
|
||||
- (void)launchGameEngine: (NSNotification*) note
|
||||
{
|
||||
auto *drv = static_cast<VideoDriver_Cocoa *>(VideoDriver::GetInstance());
|
||||
|
||||
/* Setup cursor for the current _game_mode. */
|
||||
[ _cocoa_subdriver->cocoaview resetCursorRects ];
|
||||
NSEvent *e = [ [ NSEvent alloc ] init ];
|
||||
[ drv->cocoaview cursorUpdate:e ];
|
||||
[ e release ];
|
||||
|
||||
/* Hand off to main application code. */
|
||||
static_cast<VideoDriver_Cocoa *>(VideoDriver::GetInstance())->GameLoop();
|
||||
drv->GameLoop();
|
||||
|
||||
/* We are done, thank you for playing. */
|
||||
[ self performSelectorOnMainThread:@selector(stopEngine) withObject:nil waitUntilDone:FALSE ];
|
||||
@@ -98,7 +177,7 @@ static OTTDMain *_ottd_main;
|
||||
{
|
||||
HandleExitGameRequest();
|
||||
|
||||
return NSTerminateCancel; // NSTerminateLater ?
|
||||
return NSTerminateCancel;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -203,6 +282,7 @@ bool CocoaSetupApplication()
|
||||
}
|
||||
|
||||
/* Become the front process, important when start from the command line. */
|
||||
[ [ NSApplication sharedApplication ] setActivationPolicy:NSApplicationActivationPolicyRegular ];
|
||||
[ [ NSApplication sharedApplication ] activateIgnoringOtherApps:YES ];
|
||||
|
||||
/* Set up the menubar */
|
||||
@@ -218,7 +298,7 @@ bool CocoaSetupApplication()
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Deregister app delegate.
|
||||
*/
|
||||
void CocoaExitApplication()
|
||||
{
|
||||
@@ -247,15 +327,17 @@ void CocoaDialog(const char *title, const char *message, const char *buttonLabel
|
||||
return;
|
||||
}
|
||||
|
||||
NSAlert *alert = [ [ NSAlert alloc ] init ];
|
||||
[ alert setAlertStyle: NSCriticalAlertStyle ];
|
||||
[ alert setMessageText:[ NSString stringWithUTF8String:title ] ];
|
||||
[ alert setInformativeText:[ NSString stringWithUTF8String:message ] ];
|
||||
[ alert addButtonWithTitle: [ NSString stringWithUTF8String:buttonLabel ] ];
|
||||
[ alert runModal ];
|
||||
[ alert release ];
|
||||
@autoreleasepool {
|
||||
NSAlert *alert = [ [ NSAlert alloc ] init ];
|
||||
[ alert setAlertStyle: NSCriticalAlertStyle ];
|
||||
[ alert setMessageText:[ NSString stringWithUTF8String:title ] ];
|
||||
[ alert setInformativeText:[ NSString stringWithUTF8String:message ] ];
|
||||
[ alert addButtonWithTitle: [ NSString stringWithUTF8String:buttonLabel ] ];
|
||||
[ alert runModal ];
|
||||
[ alert release ];
|
||||
}
|
||||
|
||||
if (!wasstarted && VideoDriver::GetInstance() != NULL) VideoDriver::GetInstance()->Stop();
|
||||
if (!wasstarted && VideoDriver::GetInstance() != nullptr) VideoDriver::GetInstance()->Stop();
|
||||
|
||||
_cocoa_video_dialog = false;
|
||||
}
|
||||
@@ -280,21 +362,28 @@ void CocoaDialog(const char *title, const char *message, const char *buttonLabel
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation OTTD_CocoaWindow
|
||||
|
||||
- (void)setDriver:(CocoaSubdriver*)drv
|
||||
{
|
||||
driver = drv;
|
||||
@implementation OTTD_CocoaWindow {
|
||||
VideoDriver_Cocoa *driver;
|
||||
}
|
||||
/**
|
||||
* Minimize the window
|
||||
*/
|
||||
- (void)miniaturize:(id)sender
|
||||
{
|
||||
/* window is hidden now */
|
||||
driver->active = false;
|
||||
|
||||
[ super miniaturize:sender ];
|
||||
/**
|
||||
* Initialize event system for the application rectangle
|
||||
*/
|
||||
- (instancetype)initWithContentRect:(NSRect)contentRect styleMask:(NSUInteger)styleMask backing:(NSBackingStoreType)backingType defer:(BOOL)flag driver:(VideoDriver_Cocoa *)drv
|
||||
{
|
||||
if (self = [ super initWithContentRect:contentRect styleMask:styleMask backing:backingType defer:flag ]) {
|
||||
self->driver = drv;
|
||||
|
||||
[ self setContentMinSize:NSMakeSize(64.0f, 64.0f) ];
|
||||
|
||||
std::string caption = std::string{"OpenTTD "} + _openttd_revision;
|
||||
NSString *nsscaption = [ [ NSString alloc ] initWithUTF8String:caption.c_str() ];
|
||||
[ self setTitle:nsscaption ];
|
||||
[ self setMiniwindowTitle:nsscaption ];
|
||||
[ nsscaption release ];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -313,9 +402,6 @@ void CocoaDialog(const char *title, const char *message, const char *buttonLabel
|
||||
|
||||
/* restore visible surface */
|
||||
[ self restoreCachedImage ];
|
||||
|
||||
/* window is visible again */
|
||||
driver->active = true;
|
||||
}
|
||||
/**
|
||||
* Define the rectangle we draw our window in
|
||||
@@ -324,106 +410,41 @@ void CocoaDialog(const char *title, const char *message, const char *buttonLabel
|
||||
{
|
||||
[ super setFrame:frameRect display:flag ];
|
||||
|
||||
/* Don't do anything if the window is currently being created */
|
||||
if (driver->setup) return;
|
||||
|
||||
if (!driver->WindowResized()) error("Cocoa: Failed to resize window.");
|
||||
}
|
||||
/**
|
||||
* Handle hiding of the application
|
||||
*/
|
||||
- (void)appDidHide:(NSNotification*)note
|
||||
{
|
||||
driver->active = false;
|
||||
}
|
||||
/**
|
||||
* Unhide and restore display plane and re-activate driver
|
||||
*/
|
||||
- (void)appDidUnhide:(NSNotification*)note
|
||||
{
|
||||
driver->active = true;
|
||||
}
|
||||
/**
|
||||
* Initialize event system for the application rectangle
|
||||
*/
|
||||
- (id)initWithContentRect:(NSRect)contentRect styleMask:(NSUInteger)styleMask backing:(NSBackingStoreType)backingType defer:(BOOL)flag
|
||||
{
|
||||
/* Make our window subclass receive these application notifications */
|
||||
[ [ NSNotificationCenter defaultCenter ] addObserver:self
|
||||
selector:@selector(appDidHide:) name:NSApplicationDidHideNotification object:NSApp ];
|
||||
|
||||
[ [ NSNotificationCenter defaultCenter ] addObserver:self
|
||||
selector:@selector(appDidUnhide:) name:NSApplicationDidUnhideNotification object:NSApp ];
|
||||
|
||||
return [ super initWithContentRect:contentRect styleMask:styleMask backing:backingType defer:flag ];
|
||||
driver->AllocateBackingStore();
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation OTTD_CocoaView {
|
||||
float _current_magnification;
|
||||
NSUInteger _current_mods;
|
||||
bool _emulated_down;
|
||||
bool _use_hidpi; ///< Render content in native resolution?
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Count the number of UTF-16 code points in a range of an UTF-8 string.
|
||||
* @param from Start of the range.
|
||||
* @param to End of the range.
|
||||
* @return Number of UTF-16 code points in the range.
|
||||
*/
|
||||
static NSUInteger CountUtf16Units(const char *from, const char *to)
|
||||
- (instancetype)initWithFrame:(NSRect)frameRect
|
||||
{
|
||||
NSUInteger i = 0;
|
||||
|
||||
while (from < to) {
|
||||
WChar c;
|
||||
size_t len = Utf8Decode(&c, from);
|
||||
i += len < 4 ? 1 : 2; // Watch for surrogate pairs.
|
||||
from += len;
|
||||
if (self = [ super initWithFrame:frameRect ]) {
|
||||
self->_use_hidpi = _allow_hidpi_window && [ self respondsToSelector:@selector(convertRectToBacking:) ] && [ self respondsToSelector:@selector(convertRectFromBacking:) ];
|
||||
}
|
||||
|
||||
return i;
|
||||
return self;
|
||||
}
|
||||
|
||||
/**
|
||||
* Advance an UTF-8 string by a number of equivalent UTF-16 code points.
|
||||
* @param str UTF-8 string.
|
||||
* @param count Number of UTF-16 code points to advance the string by.
|
||||
* @return Advanced string pointer.
|
||||
*/
|
||||
static const char *Utf8AdvanceByUtf16Units(const char *str, NSUInteger count)
|
||||
- (NSRect)getRealRect:(NSRect)rect
|
||||
{
|
||||
for (NSUInteger i = 0; i < count && *str != '\0'; ) {
|
||||
WChar c;
|
||||
size_t len = Utf8Decode(&c, str);
|
||||
i += len < 4 ? 1 : 2; // Watch for surrogates.
|
||||
str += len;
|
||||
}
|
||||
|
||||
return str;
|
||||
return _use_hidpi ? [ self convertRectToBacking:rect ] : rect;
|
||||
}
|
||||
|
||||
@implementation OTTD_CocoaView
|
||||
/**
|
||||
* Initialize the driver
|
||||
*/
|
||||
- (void)setDriver:(CocoaSubdriver*)drv
|
||||
- (NSRect)getVirtualRect:(NSRect)rect
|
||||
{
|
||||
driver = drv;
|
||||
return _use_hidpi ? [ self convertRectFromBacking:rect ] : rect;
|
||||
}
|
||||
/**
|
||||
* Define the opaqueness of the window / screen
|
||||
* @return opaqueness of window / screen
|
||||
*/
|
||||
- (BOOL)isOpaque
|
||||
|
||||
- (CGFloat)getContentsScale
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
/**
|
||||
* Draws a rectangle on the screen.
|
||||
* It's overwritten by the individual drivers but must be defined
|
||||
*/
|
||||
- (void)drawRect:(NSRect)invalidRect
|
||||
{
|
||||
return;
|
||||
return _use_hidpi && self.window != nil ? [ self.window backingScaleFactor ] : 1.0f;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow to handle events
|
||||
*/
|
||||
@@ -431,53 +452,35 @@ static const char *Utf8AdvanceByUtf16Units(const char *str, NSUInteger count)
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
/**
|
||||
* Actually handle events
|
||||
*/
|
||||
- (BOOL)becomeFirstResponder
|
||||
|
||||
- (void)setNeedsDisplayInRect:(NSRect)invalidRect
|
||||
{
|
||||
return YES;
|
||||
/* Drawing is handled by our sub-views. Just pass it along. */
|
||||
for ( NSView *v in [ self subviews ]) {
|
||||
[ v setNeedsDisplayInRect:[ v convertRect:invalidRect fromView:self ] ];
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Define the rectangle where we draw our application window
|
||||
*/
|
||||
- (void)setTrackingRect
|
||||
|
||||
/** Update mouse cursor to use for this view. */
|
||||
- (void)cursorUpdate:(NSEvent *)event
|
||||
{
|
||||
NSPoint loc = [ self convertPoint:[ [ self window ] mouseLocationOutsideOfEventStream ] fromView:nil ];
|
||||
BOOL inside = ([ self hitTest:loc ]==self);
|
||||
if (inside) [ [ self window ] makeFirstResponder:self ];
|
||||
trackingtag = [ self addTrackingRect:[ self visibleRect ] owner:self userData:nil assumeInside:inside ];
|
||||
[ (_game_mode == GM_BOOTSTRAP ? [ NSCursor arrowCursor ] : [ NSCursor clearCocoaCursor ]) set ];
|
||||
}
|
||||
/**
|
||||
* Return responsibility for the application window to system
|
||||
*/
|
||||
- (void)clearTrackingRect
|
||||
{
|
||||
[ self removeTrackingRect:trackingtag ];
|
||||
}
|
||||
/**
|
||||
* Declare responsibility for the cursor within our application rect
|
||||
*/
|
||||
- (void)resetCursorRects
|
||||
{
|
||||
[ super resetCursorRects ];
|
||||
[ self clearTrackingRect ];
|
||||
[ self setTrackingRect ];
|
||||
[ self addCursorRect:[ self bounds ] cursor:(_game_mode == GM_BOOTSTRAP ? [ NSCursor arrowCursor ] : [ NSCursor clearCocoaCursor ]) ];
|
||||
}
|
||||
/**
|
||||
* Prepare for moving the application window
|
||||
*/
|
||||
|
||||
- (void)viewWillMoveToWindow:(NSWindow *)win
|
||||
{
|
||||
if (!win && [ self window ]) [ self clearTrackingRect ];
|
||||
for (NSTrackingArea *a in [ self trackingAreas ]) {
|
||||
[ self removeTrackingArea:a ];
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Restore our responsibility for our application window after moving
|
||||
*/
|
||||
|
||||
- (void)viewDidMoveToWindow
|
||||
{
|
||||
if ([ self window ]) [ self setTrackingRect ];
|
||||
/* Install mouse tracking area. */
|
||||
NSTrackingAreaOptions track_opt = NSTrackingInVisibleRect | NSTrackingActiveInActiveApp | NSTrackingMouseEnteredAndExited | NSTrackingMouseMoved | NSTrackingCursorUpdate;
|
||||
NSTrackingArea *track = [ [ NSTrackingArea alloc ] initWithRect:[ self bounds ] options:track_opt owner:self userInfo:nil ];
|
||||
[ self addTrackingArea:track ];
|
||||
[ track release ];
|
||||
}
|
||||
/**
|
||||
* Make OpenTTD aware that it has control over the mouse
|
||||
@@ -491,10 +494,293 @@ static const char *Utf8AdvanceByUtf16Units(const char *str, NSUInteger count)
|
||||
*/
|
||||
- (void)mouseExited:(NSEvent *)theEvent
|
||||
{
|
||||
if (_cocoa_subdriver != NULL) UndrawMouseCursor();
|
||||
if ([ self window ] != nil) UndrawMouseCursor();
|
||||
_cursor.in_window = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the mouse location
|
||||
* @param event UI event
|
||||
* @return mouse location as NSPoint
|
||||
*/
|
||||
- (NSPoint)mousePositionFromEvent:(NSEvent *)e
|
||||
{
|
||||
NSPoint pt = e.locationInWindow;
|
||||
if ([ e window ] == nil) pt = [ self.window convertRectFromScreen:NSMakeRect(pt.x, pt.y, 0, 0) ].origin;
|
||||
pt = [ self convertPoint:pt fromView:nil ];
|
||||
|
||||
return [ self getRealRect:NSMakeRect(pt.x, self.bounds.size.height - pt.y, 0, 0) ].origin;
|
||||
}
|
||||
|
||||
- (void)internalMouseMoveEvent:(NSEvent *)event
|
||||
{
|
||||
if (_cursor.fix_at) {
|
||||
_cursor.UpdateCursorPositionRelative(event.deltaX * self.getContentsScale, event.deltaY * self.getContentsScale);
|
||||
} else {
|
||||
NSPoint pt = [ self mousePositionFromEvent:event ];
|
||||
_cursor.UpdateCursorPosition(pt.x, pt.y, false);
|
||||
}
|
||||
|
||||
HandleMouseEvents();
|
||||
}
|
||||
|
||||
- (void)internalMouseButtonEvent
|
||||
{
|
||||
bool cur_fix = _cursor.fix_at;
|
||||
HandleMouseEvents();
|
||||
|
||||
/* Cursor fix mode was changed, synchronize with OS. */
|
||||
if (cur_fix != _cursor.fix_at) CGAssociateMouseAndMouseCursorPosition(!_cursor.fix_at);
|
||||
}
|
||||
|
||||
- (BOOL)emulateRightButton:(NSEvent *)event
|
||||
{
|
||||
uint32 keymask = 0;
|
||||
if (_settings_client.gui.right_mouse_btn_emulation == RMBE_COMMAND) keymask |= NSCommandKeyMask;
|
||||
if (_settings_client.gui.right_mouse_btn_emulation == RMBE_CONTROL) keymask |= NSControlKeyMask;
|
||||
|
||||
return (event.modifierFlags & keymask) != 0;
|
||||
}
|
||||
|
||||
- (void)mouseMoved:(NSEvent *)event
|
||||
{
|
||||
[ self internalMouseMoveEvent:event ];
|
||||
}
|
||||
|
||||
- (void)mouseDragged:(NSEvent *)event
|
||||
{
|
||||
[ self internalMouseMoveEvent:event ];
|
||||
}
|
||||
- (void)mouseDown:(NSEvent *)event
|
||||
{
|
||||
if ([ self emulateRightButton:event ]) {
|
||||
self->_emulated_down = true;
|
||||
[ self rightMouseDown:event ];
|
||||
} else {
|
||||
_left_button_down = true;
|
||||
[ self internalMouseButtonEvent ];
|
||||
}
|
||||
}
|
||||
- (void)mouseUp:(NSEvent *)event
|
||||
{
|
||||
if (self->_emulated_down) {
|
||||
self->_emulated_down = false;
|
||||
[ self rightMouseUp:event ];
|
||||
} else {
|
||||
_left_button_down = false;
|
||||
_left_button_clicked = false;
|
||||
[ self internalMouseButtonEvent ];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)rightMouseDragged:(NSEvent *)event
|
||||
{
|
||||
[ self internalMouseMoveEvent:event ];
|
||||
}
|
||||
- (void)rightMouseDown:(NSEvent *)event
|
||||
{
|
||||
_right_button_down = true;
|
||||
_right_button_clicked = true;
|
||||
[ self internalMouseButtonEvent ];
|
||||
}
|
||||
- (void)rightMouseUp:(NSEvent *)event
|
||||
{
|
||||
_right_button_down = false;
|
||||
[ self internalMouseButtonEvent ];
|
||||
}
|
||||
|
||||
- (void)scrollWheel:(NSEvent *)event
|
||||
{
|
||||
if ([ event deltaY ] > 0.0) { /* Scroll up */
|
||||
_cursor.wheel--;
|
||||
} else if ([ event deltaY ] < 0.0) { /* Scroll down */
|
||||
_cursor.wheel++;
|
||||
} /* else: deltaY was 0.0 and we don't want to do anything */
|
||||
|
||||
/* Update the scroll count for 2D scrolling */
|
||||
CGFloat deltaX;
|
||||
CGFloat deltaY;
|
||||
|
||||
/* Use precise scrolling-specific deltas if they're supported. */
|
||||
if ([ event respondsToSelector:@selector(hasPreciseScrollingDeltas) ]) {
|
||||
/* No precise deltas indicates a scroll wheel is being used, so we don't want 2D scrolling. */
|
||||
if (![ event hasPreciseScrollingDeltas ]) return;
|
||||
|
||||
deltaX = [ event scrollingDeltaX ] * 0.5f;
|
||||
deltaY = [ event scrollingDeltaY ] * 0.5f;
|
||||
} else {
|
||||
deltaX = [ event deltaX ] * 5;
|
||||
deltaY = [ event deltaY ] * 5;
|
||||
}
|
||||
|
||||
_cursor.h_wheel -= (int)(deltaX * _settings_client.gui.scrollwheel_multiplier);
|
||||
_cursor.v_wheel -= (int)(deltaY * _settings_client.gui.scrollwheel_multiplier);
|
||||
}
|
||||
|
||||
- (void)magnifyWithEvent:(NSEvent *)event
|
||||
{
|
||||
/* Pinch open or close gesture. */
|
||||
self->_current_magnification += [ event magnification ] * 5.0f;
|
||||
|
||||
while (self->_current_magnification >= 1.0f) {
|
||||
self->_current_magnification -= 1.0f;
|
||||
_cursor.wheel--;
|
||||
HandleMouseEvents();
|
||||
}
|
||||
while (self->_current_magnification <= -1.0f) {
|
||||
self->_current_magnification += 1.0f;
|
||||
_cursor.wheel++;
|
||||
HandleMouseEvents();
|
||||
}
|
||||
}
|
||||
|
||||
- (void)endGestureWithEvent:(NSEvent *)event
|
||||
{
|
||||
/* Gesture ended. */
|
||||
self->_current_magnification = 0.0f;
|
||||
}
|
||||
|
||||
|
||||
- (BOOL)internalHandleKeycode:(unsigned short)keycode unicode:(WChar)unicode pressed:(BOOL)down modifiers:(NSUInteger)modifiers
|
||||
{
|
||||
switch (keycode) {
|
||||
case QZ_UP: SB(_dirkeys, 1, 1, down); break;
|
||||
case QZ_DOWN: SB(_dirkeys, 3, 1, down); break;
|
||||
case QZ_LEFT: SB(_dirkeys, 0, 1, down); break;
|
||||
case QZ_RIGHT: SB(_dirkeys, 2, 1, down); break;
|
||||
|
||||
case QZ_TAB: _tab_is_down = down; break;
|
||||
|
||||
case QZ_RETURN:
|
||||
case QZ_f:
|
||||
if (down && (modifiers & NSCommandKeyMask)) {
|
||||
VideoDriver::GetInstance()->ToggleFullscreen(!_fullscreen);
|
||||
}
|
||||
break;
|
||||
|
||||
case QZ_v:
|
||||
if (down && EditBoxInGlobalFocus() && (modifiers & (NSCommandKeyMask | NSControlKeyMask))) {
|
||||
HandleKeypress(WKC_CTRL | 'V', unicode);
|
||||
}
|
||||
break;
|
||||
case QZ_u:
|
||||
if (down && EditBoxInGlobalFocus() && (modifiers & (NSCommandKeyMask | NSControlKeyMask))) {
|
||||
HandleKeypress(WKC_CTRL | 'U', unicode);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
BOOL interpret_keys = YES;
|
||||
if (down) {
|
||||
/* Map keycode to OTTD code. */
|
||||
auto vk = std::find_if(std::begin(_vk_mapping), std::end(_vk_mapping), [=](const CocoaVkMapping &m) { return m.vk_from == keycode; });
|
||||
uint32 pressed_key = vk != std::end(_vk_mapping) ? vk->map_to : 0;
|
||||
|
||||
if (modifiers & NSShiftKeyMask) pressed_key |= WKC_SHIFT;
|
||||
if (modifiers & NSControlKeyMask) pressed_key |= (_settings_client.gui.right_mouse_btn_emulation != RMBE_CONTROL ? WKC_CTRL : WKC_META);
|
||||
if (modifiers & NSAlternateKeyMask) pressed_key |= WKC_ALT;
|
||||
if (modifiers & NSCommandKeyMask) pressed_key |= (_settings_client.gui.right_mouse_btn_emulation != RMBE_CONTROL ? WKC_META : WKC_CTRL);
|
||||
|
||||
static bool console = false;
|
||||
|
||||
/* The second backquote may have a character, which we don't want to interpret. */
|
||||
if (pressed_key == WKC_BACKQUOTE && (console || unicode == 0)) {
|
||||
if (!console) {
|
||||
/* Backquote is a dead key, require a double press for hotkey behaviour (i.e. console). */
|
||||
console = true;
|
||||
return YES;
|
||||
} else {
|
||||
/* Second backquote, don't interpret as text input. */
|
||||
interpret_keys = NO;
|
||||
}
|
||||
}
|
||||
console = false;
|
||||
|
||||
/* Don't handle normal characters if an edit box has the focus. */
|
||||
if (!EditBoxInGlobalFocus() || IsInsideMM(pressed_key & ~WKC_SPECIAL_KEYS, WKC_F1, WKC_PAUSE + 1)) {
|
||||
HandleKeypress(pressed_key, unicode);
|
||||
}
|
||||
DEBUG(driver, 2, "cocoa_v: QZ_KeyEvent: %x (%x), down, mapping: %x", keycode, unicode, pressed_key);
|
||||
} else {
|
||||
DEBUG(driver, 2, "cocoa_v: QZ_KeyEvent: %x (%x), up", keycode, unicode);
|
||||
}
|
||||
|
||||
return interpret_keys;
|
||||
}
|
||||
|
||||
- (void)keyDown:(NSEvent *)event
|
||||
{
|
||||
/* Quit, hide and minimize */
|
||||
switch (event.keyCode) {
|
||||
case QZ_q:
|
||||
case QZ_h:
|
||||
case QZ_m:
|
||||
if (event.modifierFlags & NSCommandKeyMask) {
|
||||
[ self interpretKeyEvents:[ NSArray arrayWithObject:event ] ];
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
/* Convert UTF-16 characters to UCS-4 chars. */
|
||||
std::vector<WChar> unicode_str = NSStringToUTF32([ event characters ]);
|
||||
if (unicode_str.empty()) unicode_str.push_back(0);
|
||||
|
||||
if (EditBoxInGlobalFocus()) {
|
||||
if ([ self internalHandleKeycode:event.keyCode unicode:unicode_str[0] pressed:YES modifiers:event.modifierFlags ]) {
|
||||
[ self interpretKeyEvents:[ NSArray arrayWithObject:event ] ];
|
||||
}
|
||||
} else {
|
||||
[ self internalHandleKeycode:event.keyCode unicode:unicode_str[0] pressed:YES modifiers:event.modifierFlags ];
|
||||
for (size_t i = 1; i < unicode_str.size(); i++) {
|
||||
[ self internalHandleKeycode:0 unicode:unicode_str[i] pressed:YES modifiers:event.modifierFlags ];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)keyUp:(NSEvent *)event
|
||||
{
|
||||
/* Quit, hide and minimize */
|
||||
switch (event.keyCode) {
|
||||
case QZ_q:
|
||||
case QZ_h:
|
||||
case QZ_m:
|
||||
if (event.modifierFlags & NSCommandKeyMask) {
|
||||
[ self interpretKeyEvents:[ NSArray arrayWithObject:event ] ];
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
/* Convert UTF-16 characters to UCS-4 chars. */
|
||||
std::vector<WChar> unicode_str = NSStringToUTF32([ event characters ]);
|
||||
if (unicode_str.empty()) unicode_str.push_back(0);
|
||||
|
||||
[ self internalHandleKeycode:event.keyCode unicode:unicode_str[0] pressed:NO modifiers:event.modifierFlags ];
|
||||
}
|
||||
|
||||
- (void)flagsChanged:(NSEvent *)event
|
||||
{
|
||||
const int mapping[] = { QZ_CAPSLOCK, QZ_LSHIFT, QZ_LCTRL, QZ_LALT, QZ_LMETA };
|
||||
|
||||
NSUInteger newMods = event.modifierFlags;
|
||||
if (self->_current_mods == newMods) return;
|
||||
|
||||
/* Iterate through the bits, testing each against the current modifiers */
|
||||
for (unsigned int i = 0, bit = NSAlphaShiftKeyMask; bit <= NSCommandKeyMask; bit <<= 1, ++i) {
|
||||
unsigned int currentMask, newMask;
|
||||
|
||||
currentMask = self->_current_mods & bit;
|
||||
newMask = newMods & bit;
|
||||
|
||||
if (currentMask && currentMask != newMask) { // modifier up event
|
||||
[ self internalHandleKeycode:mapping[i] unicode:0 pressed:NO modifiers:newMods ];
|
||||
} else if (newMask && currentMask != newMask) { // modifier down event
|
||||
[ self internalHandleKeycode:mapping[i] unicode:0 pressed:YES modifiers:newMods ];
|
||||
}
|
||||
}
|
||||
|
||||
_current_mods = newMods;
|
||||
}
|
||||
|
||||
|
||||
/** Insert the given text at the given range. */
|
||||
- (void)insertText:(id)aString replacementRange:(NSRange)replacementRange
|
||||
@@ -555,7 +841,7 @@ static const char *Utf8AdvanceByUtf16Units(const char *str, NSUInteger count)
|
||||
/** Unmark the current marked text. */
|
||||
- (void)unmarkText
|
||||
{
|
||||
HandleTextInput(NULL, true);
|
||||
HandleTextInput(nullptr, true);
|
||||
}
|
||||
|
||||
/** Get the caret position. */
|
||||
@@ -574,7 +860,7 @@ static const char *Utf8AdvanceByUtf16Units(const char *str, NSUInteger count)
|
||||
|
||||
size_t mark_len;
|
||||
const char *mark = _focused_window->GetMarkedText(&mark_len);
|
||||
if (mark != NULL) {
|
||||
if (mark != nullptr) {
|
||||
NSUInteger start = CountUtf16Units(_focused_window->GetFocusedText(), mark);
|
||||
NSUInteger len = CountUtf16Units(mark, mark + mark_len);
|
||||
|
||||
@@ -590,7 +876,7 @@ static const char *Utf8AdvanceByUtf16Units(const char *str, NSUInteger count)
|
||||
if (!EditBoxInGlobalFocus()) return NO;
|
||||
|
||||
size_t len;
|
||||
return _focused_window->GetMarkedText(&len) != NULL;
|
||||
return _focused_window->GetMarkedText(&len) != nullptr;
|
||||
}
|
||||
|
||||
/** Get a string corresponding to the given range. */
|
||||
@@ -601,7 +887,7 @@ static const char *Utf8AdvanceByUtf16Units(const char *str, NSUInteger count)
|
||||
NSString *s = [ NSString stringWithUTF8String:_focused_window->GetFocusedText() ];
|
||||
NSRange valid_range = NSIntersectionRange(NSMakeRange(0, [ s length ]), theRange);
|
||||
|
||||
if (actualRange != NULL) *actualRange = valid_range;
|
||||
if (actualRange != nullptr) *actualRange = valid_range;
|
||||
if (valid_range.length == 0) return nil;
|
||||
|
||||
return [ [ [ NSAttributedString alloc ] initWithString:[ s substringWithRange:valid_range ] ] autorelease ];
|
||||
@@ -610,7 +896,7 @@ static const char *Utf8AdvanceByUtf16Units(const char *str, NSUInteger count)
|
||||
/** Get a string corresponding to the given range. */
|
||||
- (NSAttributedString *)attributedSubstringFromRange:(NSRange)theRange
|
||||
{
|
||||
return [ self attributedSubstringForProposedRange:theRange actualRange:NULL ];
|
||||
return [ self attributedSubstringForProposedRange:theRange actualRange:nil ];
|
||||
}
|
||||
|
||||
/** Get the current edit box string. */
|
||||
@@ -631,7 +917,7 @@ static const char *Utf8AdvanceByUtf16Units(const char *str, NSUInteger count)
|
||||
Point pt = { (int)view_pt.x, (int)[ self frame ].size.height - (int)view_pt.y };
|
||||
|
||||
const char *ch = _focused_window->GetTextCharacterAtPosition(pt);
|
||||
if (ch == NULL) return NSNotFound;
|
||||
if (ch == nullptr) return NSNotFound;
|
||||
|
||||
return CountUtf16Units(_focused_window->GetFocusedText(), ch);
|
||||
}
|
||||
@@ -807,12 +1093,17 @@ static const char *Utf8AdvanceByUtf16Units(const char *str, NSUInteger count)
|
||||
@end
|
||||
|
||||
|
||||
@implementation OTTD_CocoaWindowDelegate {
|
||||
VideoDriver_Cocoa *driver;
|
||||
}
|
||||
|
||||
@implementation OTTD_CocoaWindowDelegate
|
||||
/** Initialize the video driver */
|
||||
- (void)setDriver:(CocoaSubdriver*)drv
|
||||
- (instancetype)initWithDriver:(VideoDriver_Cocoa *)drv
|
||||
{
|
||||
driver = drv;
|
||||
if (self = [ super init ]) {
|
||||
self->driver = drv;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
/** Handle closure requests */
|
||||
- (BOOL)windowShouldClose:(id)sender
|
||||
@@ -821,26 +1112,6 @@ static const char *Utf8AdvanceByUtf16Units(const char *str, NSUInteger count)
|
||||
|
||||
return NO;
|
||||
}
|
||||
/** Handle key acceptance */
|
||||
- (void)windowDidBecomeKey:(NSNotification*)aNotification
|
||||
{
|
||||
driver->active = true;
|
||||
}
|
||||
/** Resign key acceptance */
|
||||
- (void)windowDidResignKey:(NSNotification*)aNotification
|
||||
{
|
||||
driver->active = false;
|
||||
}
|
||||
/** Handle becoming main window */
|
||||
- (void)windowDidBecomeMain:(NSNotification*)aNotification
|
||||
{
|
||||
driver->active = true;
|
||||
}
|
||||
/** Resign being main window */
|
||||
- (void)windowDidResignMain:(NSNotification*)aNotification
|
||||
{
|
||||
driver->active = false;
|
||||
}
|
||||
/** Window entered fullscreen mode (10.7). */
|
||||
- (void)windowDidEnterFullScreen:(NSNotification *)aNotification
|
||||
{
|
||||
@@ -854,10 +1125,11 @@ static const char *Utf8AdvanceByUtf16Units(const char *str, NSUInteger count)
|
||||
[ e release ];
|
||||
}
|
||||
}
|
||||
/** The colour profile of the screen the window is on changed. */
|
||||
- (void)windowDidChangeScreenProfile:(NSNotification *)aNotification
|
||||
/** Screen the window is on changed. */
|
||||
- (void)windowDidChangeBackingProperties:(NSNotification *)notification
|
||||
{
|
||||
if (!driver->setup) driver->WindowResized();
|
||||
/* Reallocate screen buffer if necessary. */
|
||||
driver->AllocateBackingStore();
|
||||
}
|
||||
|
||||
/** Presentation options to use for fullsreen mode. */
|
||||
@@ -867,4 +1139,5 @@ static const char *Utf8AdvanceByUtf16Units(const char *str, NSUInteger count)
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#endif /* WITH_COCOA */
|
||||
|
||||
@@ -1,720 +0,0 @@
|
||||
/*
|
||||
* This file is part of OpenTTD.
|
||||
* OpenTTD 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, version 2.
|
||||
* OpenTTD 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 OpenTTD. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/******************************************************************************
|
||||
* Cocoa video driver *
|
||||
* Known things left to do: *
|
||||
* Nothing at the moment. *
|
||||
******************************************************************************/
|
||||
|
||||
#ifdef WITH_COCOA
|
||||
|
||||
#include "../../stdafx.h"
|
||||
|
||||
#define Rect OTTDRect
|
||||
#define Point OTTDPoint
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#undef Rect
|
||||
#undef Point
|
||||
|
||||
#include "../../openttd.h"
|
||||
#include "../../debug.h"
|
||||
#include "../../os/macosx/splash.h"
|
||||
#include "../../settings_type.h"
|
||||
#include "../../core/geometry_type.hpp"
|
||||
#include "cocoa_v.h"
|
||||
#include "cocoa_keys.h"
|
||||
#include "../../blitter/factory.hpp"
|
||||
#include "../../gfx_func.h"
|
||||
#include "../../network/network.h"
|
||||
#include "../../core/random_func.hpp"
|
||||
#include "../../core/math_func.hpp"
|
||||
#include "../../texteff.hpp"
|
||||
#include "../../window_func.h"
|
||||
#include "../../thread.h"
|
||||
|
||||
#import <sys/time.h> /* gettimeofday */
|
||||
|
||||
/**
|
||||
* Important notice regarding all modifications!!!!!!!
|
||||
* There are certain limitations because the file is objective C++.
|
||||
* gdb has limitations.
|
||||
* C++ and objective C code can't be joined in all cases (classes stuff).
|
||||
* Read http://developer.apple.com/releasenotes/Cocoa/Objective-C++.html for more information.
|
||||
*/
|
||||
|
||||
|
||||
/* Right Mouse Button Emulation enum */
|
||||
enum RightMouseButtonEmulationState {
|
||||
RMBE_COMMAND,
|
||||
RMBE_CONTROL,
|
||||
RMBE_OFF,
|
||||
};
|
||||
|
||||
|
||||
static unsigned int _current_mods;
|
||||
static bool _tab_is_down;
|
||||
static bool _emulating_right_button;
|
||||
static float _current_magnification;
|
||||
#ifdef _DEBUG
|
||||
static uint32 _tEvent;
|
||||
#endif
|
||||
|
||||
|
||||
static uint32 GetTick()
|
||||
{
|
||||
struct timeval tim;
|
||||
|
||||
gettimeofday(&tim, nullptr);
|
||||
return tim.tv_usec / 1000 + tim.tv_sec * 1000;
|
||||
}
|
||||
|
||||
static void QZ_WarpCursor(int x, int y)
|
||||
{
|
||||
assert(_cocoa_subdriver != nullptr);
|
||||
|
||||
/* Only allow warping when in foreground */
|
||||
if (![ NSApp isActive ]) return;
|
||||
|
||||
NSPoint p = NSMakePoint(x, y);
|
||||
CGPoint cgp = _cocoa_subdriver->PrivateLocalToCG(&p);
|
||||
|
||||
/* Do the actual warp */
|
||||
CGWarpMouseCursorPosition(cgp);
|
||||
/* this is the magic call that fixes cursor "freezing" after warp */
|
||||
CGAssociateMouseAndMouseCursorPosition(true);
|
||||
}
|
||||
|
||||
|
||||
static void QZ_CheckPaletteAnim()
|
||||
{
|
||||
if (_cur_palette.count_dirty != 0) {
|
||||
Blitter *blitter = BlitterFactory::GetCurrentBlitter();
|
||||
|
||||
switch (blitter->UsePaletteAnimation()) {
|
||||
case Blitter::PALETTE_ANIMATION_VIDEO_BACKEND:
|
||||
_cocoa_subdriver->UpdatePalette(_cur_palette.first_dirty, _cur_palette.count_dirty);
|
||||
break;
|
||||
|
||||
case Blitter::PALETTE_ANIMATION_BLITTER:
|
||||
blitter->PaletteAnimate(_cur_palette);
|
||||
break;
|
||||
|
||||
case Blitter::PALETTE_ANIMATION_NONE:
|
||||
break;
|
||||
|
||||
default:
|
||||
NOT_REACHED();
|
||||
}
|
||||
_cur_palette.count_dirty = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
struct VkMapping {
|
||||
unsigned short vk_from;
|
||||
byte map_to;
|
||||
};
|
||||
|
||||
#define AS(x, z) {x, z}
|
||||
|
||||
static const VkMapping _vk_mapping[] = {
|
||||
AS(QZ_BACKQUOTE, WKC_BACKQUOTE), // key left of '1'
|
||||
AS(QZ_BACKQUOTE2, WKC_BACKQUOTE), // some keyboards have it on another scancode
|
||||
|
||||
/* Pageup stuff + up/down */
|
||||
AS(QZ_PAGEUP, WKC_PAGEUP),
|
||||
AS(QZ_PAGEDOWN, WKC_PAGEDOWN),
|
||||
|
||||
AS(QZ_UP, WKC_UP),
|
||||
AS(QZ_DOWN, WKC_DOWN),
|
||||
AS(QZ_LEFT, WKC_LEFT),
|
||||
AS(QZ_RIGHT, WKC_RIGHT),
|
||||
|
||||
AS(QZ_HOME, WKC_HOME),
|
||||
AS(QZ_END, WKC_END),
|
||||
|
||||
AS(QZ_INSERT, WKC_INSERT),
|
||||
AS(QZ_DELETE, WKC_DELETE),
|
||||
|
||||
/* Letters. QZ_[a-z] is not in numerical order so we can't use AM(...) */
|
||||
AS(QZ_a, 'A'),
|
||||
AS(QZ_b, 'B'),
|
||||
AS(QZ_c, 'C'),
|
||||
AS(QZ_d, 'D'),
|
||||
AS(QZ_e, 'E'),
|
||||
AS(QZ_f, 'F'),
|
||||
AS(QZ_g, 'G'),
|
||||
AS(QZ_h, 'H'),
|
||||
AS(QZ_i, 'I'),
|
||||
AS(QZ_j, 'J'),
|
||||
AS(QZ_k, 'K'),
|
||||
AS(QZ_l, 'L'),
|
||||
AS(QZ_m, 'M'),
|
||||
AS(QZ_n, 'N'),
|
||||
AS(QZ_o, 'O'),
|
||||
AS(QZ_p, 'P'),
|
||||
AS(QZ_q, 'Q'),
|
||||
AS(QZ_r, 'R'),
|
||||
AS(QZ_s, 'S'),
|
||||
AS(QZ_t, 'T'),
|
||||
AS(QZ_u, 'U'),
|
||||
AS(QZ_v, 'V'),
|
||||
AS(QZ_w, 'W'),
|
||||
AS(QZ_x, 'X'),
|
||||
AS(QZ_y, 'Y'),
|
||||
AS(QZ_z, 'Z'),
|
||||
/* Same thing for digits */
|
||||
AS(QZ_0, '0'),
|
||||
AS(QZ_1, '1'),
|
||||
AS(QZ_2, '2'),
|
||||
AS(QZ_3, '3'),
|
||||
AS(QZ_4, '4'),
|
||||
AS(QZ_5, '5'),
|
||||
AS(QZ_6, '6'),
|
||||
AS(QZ_7, '7'),
|
||||
AS(QZ_8, '8'),
|
||||
AS(QZ_9, '9'),
|
||||
|
||||
AS(QZ_ESCAPE, WKC_ESC),
|
||||
AS(QZ_PAUSE, WKC_PAUSE),
|
||||
AS(QZ_BACKSPACE, WKC_BACKSPACE),
|
||||
|
||||
AS(QZ_SPACE, WKC_SPACE),
|
||||
AS(QZ_RETURN, WKC_RETURN),
|
||||
AS(QZ_TAB, WKC_TAB),
|
||||
|
||||
/* Function keys */
|
||||
AS(QZ_F1, WKC_F1),
|
||||
AS(QZ_F2, WKC_F2),
|
||||
AS(QZ_F3, WKC_F3),
|
||||
AS(QZ_F4, WKC_F4),
|
||||
AS(QZ_F5, WKC_F5),
|
||||
AS(QZ_F6, WKC_F6),
|
||||
AS(QZ_F7, WKC_F7),
|
||||
AS(QZ_F8, WKC_F8),
|
||||
AS(QZ_F9, WKC_F9),
|
||||
AS(QZ_F10, WKC_F10),
|
||||
AS(QZ_F11, WKC_F11),
|
||||
AS(QZ_F12, WKC_F12),
|
||||
|
||||
/* Numeric part */
|
||||
AS(QZ_KP0, '0'),
|
||||
AS(QZ_KP1, '1'),
|
||||
AS(QZ_KP2, '2'),
|
||||
AS(QZ_KP3, '3'),
|
||||
AS(QZ_KP4, '4'),
|
||||
AS(QZ_KP5, '5'),
|
||||
AS(QZ_KP6, '6'),
|
||||
AS(QZ_KP7, '7'),
|
||||
AS(QZ_KP8, '8'),
|
||||
AS(QZ_KP9, '9'),
|
||||
AS(QZ_KP_DIVIDE, WKC_NUM_DIV),
|
||||
AS(QZ_KP_MULTIPLY, WKC_NUM_MUL),
|
||||
AS(QZ_KP_MINUS, WKC_NUM_MINUS),
|
||||
AS(QZ_KP_PLUS, WKC_NUM_PLUS),
|
||||
AS(QZ_KP_ENTER, WKC_NUM_ENTER),
|
||||
AS(QZ_KP_PERIOD, WKC_NUM_DECIMAL),
|
||||
|
||||
/* Other non-letter keys */
|
||||
AS(QZ_SLASH, WKC_SLASH),
|
||||
AS(QZ_SEMICOLON, WKC_SEMICOLON),
|
||||
AS(QZ_EQUALS, WKC_EQUALS),
|
||||
AS(QZ_LEFTBRACKET, WKC_L_BRACKET),
|
||||
AS(QZ_BACKSLASH, WKC_BACKSLASH),
|
||||
AS(QZ_RIGHTBRACKET, WKC_R_BRACKET),
|
||||
|
||||
AS(QZ_QUOTE, WKC_SINGLEQUOTE),
|
||||
AS(QZ_COMMA, WKC_COMMA),
|
||||
AS(QZ_MINUS, WKC_MINUS),
|
||||
AS(QZ_PERIOD, WKC_PERIOD)
|
||||
};
|
||||
|
||||
|
||||
static uint32 QZ_MapKey(unsigned short sym)
|
||||
{
|
||||
uint32 key = 0;
|
||||
|
||||
for (const VkMapping *map = _vk_mapping; map != endof(_vk_mapping); ++map) {
|
||||
if (sym == map->vk_from) {
|
||||
key = map->map_to;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (_current_mods & NSShiftKeyMask) key |= WKC_SHIFT;
|
||||
if (_current_mods & NSControlKeyMask) key |= (_settings_client.gui.right_mouse_btn_emulation != RMBE_CONTROL ? WKC_CTRL : WKC_META);
|
||||
if (_current_mods & NSAlternateKeyMask) key |= WKC_ALT;
|
||||
if (_current_mods & NSCommandKeyMask) key |= (_settings_client.gui.right_mouse_btn_emulation != RMBE_CONTROL ? WKC_META : WKC_CTRL);
|
||||
|
||||
return key;
|
||||
}
|
||||
|
||||
static bool QZ_KeyEvent(unsigned short keycode, unsigned short unicode, BOOL down)
|
||||
{
|
||||
bool interpret_keys = true;
|
||||
|
||||
switch (keycode) {
|
||||
case QZ_UP: SB(_dirkeys, 1, 1, down); break;
|
||||
case QZ_DOWN: SB(_dirkeys, 3, 1, down); break;
|
||||
case QZ_LEFT: SB(_dirkeys, 0, 1, down); break;
|
||||
case QZ_RIGHT: SB(_dirkeys, 2, 1, down); break;
|
||||
|
||||
case QZ_TAB: _tab_is_down = down; break;
|
||||
|
||||
case QZ_RETURN:
|
||||
case QZ_f:
|
||||
if (down && (_current_mods & NSCommandKeyMask)) {
|
||||
VideoDriver::GetInstance()->ToggleFullscreen(!_fullscreen);
|
||||
}
|
||||
break;
|
||||
|
||||
case QZ_v:
|
||||
if (down && EditBoxInGlobalFocus() && (_current_mods & (NSCommandKeyMask | NSControlKeyMask))) {
|
||||
HandleKeypress(WKC_CTRL | 'V', unicode);
|
||||
}
|
||||
break;
|
||||
case QZ_u:
|
||||
if (down && EditBoxInGlobalFocus() && (_current_mods & (NSCommandKeyMask | NSControlKeyMask))) {
|
||||
HandleKeypress(WKC_CTRL | 'U', unicode);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (down) {
|
||||
uint32 pressed_key = QZ_MapKey(keycode);
|
||||
|
||||
static bool console = false;
|
||||
|
||||
/* The second backquote may have a character, which we don't want to interpret. */
|
||||
if (pressed_key == WKC_BACKQUOTE && (console || unicode == 0)) {
|
||||
if (!console) {
|
||||
/* Backquote is a dead key, require a double press for hotkey behaviour (i.e. console). */
|
||||
console = true;
|
||||
return true;
|
||||
} else {
|
||||
/* Second backquote, don't interpret as text input. */
|
||||
interpret_keys = false;
|
||||
}
|
||||
}
|
||||
console = false;
|
||||
|
||||
/* Don't handle normal characters if an edit box has the focus. */
|
||||
if (!EditBoxInGlobalFocus() || IsInsideMM(pressed_key & ~WKC_SPECIAL_KEYS, WKC_F1, WKC_PAUSE + 1)) {
|
||||
HandleKeypress(pressed_key, unicode);
|
||||
}
|
||||
DEBUG(driver, 2, "cocoa_v: QZ_KeyEvent: %x (%x), down, mapping: %x", keycode, unicode, pressed_key);
|
||||
} else {
|
||||
DEBUG(driver, 2, "cocoa_v: QZ_KeyEvent: %x (%x), up", keycode, unicode);
|
||||
}
|
||||
|
||||
return interpret_keys;
|
||||
}
|
||||
|
||||
static void QZ_DoUnsidedModifiers(unsigned int newMods)
|
||||
{
|
||||
const int mapping[] = { QZ_CAPSLOCK, QZ_LSHIFT, QZ_LCTRL, QZ_LALT, QZ_LMETA };
|
||||
|
||||
if (_current_mods == newMods) return;
|
||||
|
||||
/* Iterate through the bits, testing each against the current modifiers */
|
||||
for (unsigned int i = 0, bit = NSAlphaShiftKeyMask; bit <= NSCommandKeyMask; bit <<= 1, ++i) {
|
||||
unsigned int currentMask, newMask;
|
||||
|
||||
currentMask = _current_mods & bit;
|
||||
newMask = newMods & bit;
|
||||
|
||||
if (currentMask && currentMask != newMask) { // modifier up event
|
||||
/* If this was Caps Lock, we need some additional voodoo to make SDL happy (is this needed in ottd?) */
|
||||
if (bit == NSAlphaShiftKeyMask) QZ_KeyEvent(mapping[i], 0, YES);
|
||||
QZ_KeyEvent(mapping[i], 0, NO);
|
||||
} else if (newMask && currentMask != newMask) { // modifier down event
|
||||
QZ_KeyEvent(mapping[i], 0, YES);
|
||||
/* If this was Caps Lock, we need some additional voodoo to make SDL happy (is this needed in ottd?) */
|
||||
if (bit == NSAlphaShiftKeyMask) QZ_KeyEvent(mapping[i], 0, NO);
|
||||
}
|
||||
}
|
||||
|
||||
_current_mods = newMods;
|
||||
}
|
||||
|
||||
static void QZ_MouseMovedEvent(int x, int y)
|
||||
{
|
||||
if (_cursor.UpdateCursorPosition(x, y, false)) {
|
||||
QZ_WarpCursor(_cursor.pos.x, _cursor.pos.y);
|
||||
}
|
||||
HandleMouseEvents();
|
||||
}
|
||||
|
||||
|
||||
static void QZ_MouseButtonEvent(int button, BOOL down)
|
||||
{
|
||||
switch (button) {
|
||||
case 0:
|
||||
if (down) {
|
||||
_left_button_down = true;
|
||||
} else {
|
||||
_left_button_down = false;
|
||||
_left_button_clicked = false;
|
||||
}
|
||||
HandleMouseEvents();
|
||||
break;
|
||||
|
||||
case 1:
|
||||
if (down) {
|
||||
_right_button_down = true;
|
||||
_right_button_clicked = true;
|
||||
} else {
|
||||
_right_button_down = false;
|
||||
}
|
||||
HandleMouseEvents();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
static bool QZ_PollEvent()
|
||||
{
|
||||
assert(_cocoa_subdriver != nullptr);
|
||||
|
||||
#ifdef _DEBUG
|
||||
uint32 et0 = GetTick();
|
||||
#endif
|
||||
NSEvent *event = [ NSApp nextEventMatchingMask:NSAnyEventMask
|
||||
untilDate:[ NSDate distantPast ]
|
||||
inMode:NSDefaultRunLoopMode dequeue:YES ];
|
||||
#ifdef _DEBUG
|
||||
_tEvent += GetTick() - et0;
|
||||
#endif
|
||||
|
||||
if (event == nil) return false;
|
||||
if (!_cocoa_subdriver->IsActive()) {
|
||||
[ NSApp sendEvent:event ];
|
||||
return true;
|
||||
}
|
||||
|
||||
QZ_DoUnsidedModifiers( [ event modifierFlags ] );
|
||||
|
||||
NSString *chars;
|
||||
NSPoint pt;
|
||||
switch ([ event type ]) {
|
||||
case NSMouseMoved:
|
||||
case NSOtherMouseDragged:
|
||||
case NSLeftMouseDragged:
|
||||
pt = _cocoa_subdriver->GetMouseLocation(event);
|
||||
if (!_cocoa_subdriver->MouseIsInsideView(&pt) && !_emulating_right_button) {
|
||||
[ NSApp sendEvent:event ];
|
||||
break;
|
||||
}
|
||||
|
||||
QZ_MouseMovedEvent((int)pt.x, (int)pt.y);
|
||||
break;
|
||||
|
||||
case NSRightMouseDragged:
|
||||
pt = _cocoa_subdriver->GetMouseLocation(event);
|
||||
QZ_MouseMovedEvent((int)pt.x, (int)pt.y);
|
||||
break;
|
||||
|
||||
case NSLeftMouseDown:
|
||||
{
|
||||
uint32 keymask = 0;
|
||||
if (_settings_client.gui.right_mouse_btn_emulation == RMBE_COMMAND) keymask |= NSCommandKeyMask;
|
||||
if (_settings_client.gui.right_mouse_btn_emulation == RMBE_CONTROL) keymask |= NSControlKeyMask;
|
||||
|
||||
pt = _cocoa_subdriver->GetMouseLocation(event);
|
||||
|
||||
if (!([ event modifierFlags ] & keymask) || !_cocoa_subdriver->MouseIsInsideView(&pt)) {
|
||||
[ NSApp sendEvent:event ];
|
||||
}
|
||||
|
||||
QZ_MouseMovedEvent((int)pt.x, (int)pt.y);
|
||||
|
||||
/* Right mouse button emulation */
|
||||
if ([ event modifierFlags ] & keymask) {
|
||||
_emulating_right_button = true;
|
||||
QZ_MouseButtonEvent(1, YES);
|
||||
} else {
|
||||
QZ_MouseButtonEvent(0, YES);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case NSLeftMouseUp:
|
||||
[ NSApp sendEvent:event ];
|
||||
|
||||
pt = _cocoa_subdriver->GetMouseLocation(event);
|
||||
|
||||
QZ_MouseMovedEvent((int)pt.x, (int)pt.y);
|
||||
|
||||
/* Right mouse button emulation */
|
||||
if (_emulating_right_button) {
|
||||
_emulating_right_button = false;
|
||||
QZ_MouseButtonEvent(1, NO);
|
||||
} else {
|
||||
QZ_MouseButtonEvent(0, NO);
|
||||
}
|
||||
break;
|
||||
|
||||
case NSRightMouseDown:
|
||||
pt = _cocoa_subdriver->GetMouseLocation(event);
|
||||
if (!_cocoa_subdriver->MouseIsInsideView(&pt)) {
|
||||
[ NSApp sendEvent:event ];
|
||||
break;
|
||||
}
|
||||
|
||||
QZ_MouseMovedEvent((int)pt.x, (int)pt.y);
|
||||
QZ_MouseButtonEvent(1, YES);
|
||||
break;
|
||||
|
||||
case NSRightMouseUp:
|
||||
pt = _cocoa_subdriver->GetMouseLocation(event);
|
||||
if (!_cocoa_subdriver->MouseIsInsideView(&pt)) {
|
||||
[ NSApp sendEvent:event ];
|
||||
break;
|
||||
}
|
||||
|
||||
QZ_MouseMovedEvent((int)pt.x, (int)pt.y);
|
||||
QZ_MouseButtonEvent(1, NO);
|
||||
break;
|
||||
|
||||
#if 0
|
||||
/* This is not needed since openttd currently only use two buttons */
|
||||
case NSOtherMouseDown:
|
||||
pt = QZ_GetMouseLocation(event);
|
||||
if (!QZ_MouseIsInsideView(&pt)) {
|
||||
[ NSApp sendEvent:event ];
|
||||
break;
|
||||
}
|
||||
|
||||
QZ_MouseMovedEvent((int)pt.x, (int)pt.y);
|
||||
QZ_MouseButtonEvent([ event buttonNumber ], YES);
|
||||
break;
|
||||
|
||||
case NSOtherMouseUp:
|
||||
pt = QZ_GetMouseLocation(event);
|
||||
if (!QZ_MouseIsInsideView(&pt)) {
|
||||
[ NSApp sendEvent:event ];
|
||||
break;
|
||||
}
|
||||
|
||||
QZ_MouseMovedEvent((int)pt.x, (int)pt.y);
|
||||
QZ_MouseButtonEvent([ event buttonNumber ], NO);
|
||||
break;
|
||||
#endif
|
||||
|
||||
case NSKeyDown: {
|
||||
/* Quit, hide and minimize */
|
||||
switch ([ event keyCode ]) {
|
||||
case QZ_q:
|
||||
case QZ_h:
|
||||
case QZ_m:
|
||||
if ([ event modifierFlags ] & NSCommandKeyMask) {
|
||||
[ NSApp sendEvent:event ];
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
chars = [ event characters ];
|
||||
unsigned short unicode = [ chars length ] > 0 ? [ chars characterAtIndex:0 ] : 0;
|
||||
if (EditBoxInGlobalFocus()) {
|
||||
if (QZ_KeyEvent([ event keyCode ], unicode, YES)) {
|
||||
[ _cocoa_subdriver->cocoaview interpretKeyEvents:[ NSArray arrayWithObject:event ] ];
|
||||
}
|
||||
} else {
|
||||
QZ_KeyEvent([ event keyCode ], unicode, YES);
|
||||
for (uint i = 1; i < [ chars length ]; i++) {
|
||||
QZ_KeyEvent(0, [ chars characterAtIndex:i ], YES);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case NSKeyUp:
|
||||
/* Quit, hide and minimize */
|
||||
switch ([ event keyCode ]) {
|
||||
case QZ_q:
|
||||
case QZ_h:
|
||||
case QZ_m:
|
||||
if ([ event modifierFlags ] & NSCommandKeyMask) {
|
||||
[ NSApp sendEvent:event ];
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
chars = [ event characters ];
|
||||
QZ_KeyEvent([ event keyCode ], [ chars length ] ? [ chars characterAtIndex:0 ] : 0, NO);
|
||||
break;
|
||||
|
||||
case NSScrollWheel:
|
||||
if ([ event deltaY ] > 0.0) { /* Scroll up */
|
||||
_cursor.wheel--;
|
||||
} else if ([ event deltaY ] < 0.0) { /* Scroll down */
|
||||
_cursor.wheel++;
|
||||
} /* else: deltaY was 0.0 and we don't want to do anything */
|
||||
|
||||
/* Update the scroll count for 2D scrolling */
|
||||
CGFloat deltaX;
|
||||
CGFloat deltaY;
|
||||
|
||||
/* Use precise scrolling-specific deltas if they're supported. */
|
||||
if ([event respondsToSelector:@selector(hasPreciseScrollingDeltas)]) {
|
||||
/* No precise deltas indicates a scroll wheel is being used, so we don't want 2D scrolling. */
|
||||
if (![ event hasPreciseScrollingDeltas ]) break;
|
||||
|
||||
deltaX = [ event scrollingDeltaX ] * 0.5f;
|
||||
deltaY = [ event scrollingDeltaY ] * 0.5f;
|
||||
} else {
|
||||
deltaX = [ event deltaX ] * 5;
|
||||
deltaY = [ event deltaY ] * 5;
|
||||
}
|
||||
|
||||
_cursor.h_wheel -= (int)(deltaX * _settings_client.gui.scrollwheel_multiplier);
|
||||
_cursor.v_wheel -= (int)(deltaY * _settings_client.gui.scrollwheel_multiplier);
|
||||
|
||||
break;
|
||||
|
||||
case NSEventTypeMagnify:
|
||||
/* Pinch open or close gesture. */
|
||||
_current_magnification += [ event magnification ] * 5.0f;
|
||||
|
||||
while (_current_magnification >= 1.0f) {
|
||||
_current_magnification -= 1.0f;
|
||||
_cursor.wheel--;
|
||||
HandleMouseEvents();
|
||||
}
|
||||
while (_current_magnification <= -1.0f) {
|
||||
_current_magnification += 1.0f;
|
||||
_cursor.wheel++;
|
||||
HandleMouseEvents();
|
||||
}
|
||||
break;
|
||||
|
||||
case NSEventTypeEndGesture:
|
||||
/* Gesture ended. */
|
||||
_current_magnification = 0.0f;
|
||||
break;
|
||||
|
||||
case NSCursorUpdate:
|
||||
case NSMouseEntered:
|
||||
case NSMouseExited:
|
||||
/* Catch these events if the cursor is dragging. During dragging, we reset
|
||||
* the mouse position programmatically, which would trigger OS X to show
|
||||
* the default arrow cursor if the events are propagated. */
|
||||
if (_cursor.fix_at) break;
|
||||
FALLTHROUGH;
|
||||
|
||||
default:
|
||||
[ NSApp sendEvent:event ];
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
void VideoDriver_Cocoa::GameLoop()
|
||||
{
|
||||
uint32 cur_ticks = GetTick();
|
||||
uint32 last_cur_ticks = cur_ticks;
|
||||
uint32 next_tick = cur_ticks + MILLISECONDS_PER_TICK;
|
||||
|
||||
#ifdef _DEBUG
|
||||
uint32 et0 = GetTick();
|
||||
uint32 st = 0;
|
||||
#endif
|
||||
|
||||
DisplaySplashImage();
|
||||
QZ_CheckPaletteAnim();
|
||||
_cocoa_subdriver->Draw(true);
|
||||
CSleep(1);
|
||||
|
||||
for (int i = 0; i < 2; i++) {
|
||||
::GameLoop();
|
||||
::GameLoopPaletteAnimations();
|
||||
}
|
||||
|
||||
UpdateWindows();
|
||||
QZ_CheckPaletteAnim();
|
||||
_cocoa_subdriver->Draw();
|
||||
CSleep(1);
|
||||
|
||||
/* Set the proper OpenTTD palette which got spoilt by the splash
|
||||
* image when using 8bpp blitter */
|
||||
GfxInitPalettes();
|
||||
QZ_CheckPaletteAnim();
|
||||
_cocoa_subdriver->Draw(true);
|
||||
|
||||
for (;;) {
|
||||
uint32 prev_cur_ticks = cur_ticks; // to check for wrapping
|
||||
InteractiveRandom(); // randomness
|
||||
|
||||
while (QZ_PollEvent()) {}
|
||||
|
||||
if (_exit_game) {
|
||||
/* Restore saved resolution if in fullscreen mode. */
|
||||
if (_cocoa_subdriver->IsFullscreen()) _cur_resolution = this->orig_res;
|
||||
break;
|
||||
}
|
||||
|
||||
#if defined(_DEBUG)
|
||||
if (_current_mods & NSShiftKeyMask)
|
||||
#else
|
||||
if (_tab_is_down)
|
||||
#endif
|
||||
{
|
||||
if (!_networking && _game_mode != GM_MENU) _fast_forward |= 2;
|
||||
} else if (_fast_forward & 2) {
|
||||
_fast_forward = 0;
|
||||
}
|
||||
|
||||
cur_ticks = GetTick();
|
||||
if (cur_ticks >= next_tick || (_fast_forward && !_pause_mode) || cur_ticks < prev_cur_ticks) {
|
||||
_realtime_tick += cur_ticks - last_cur_ticks;
|
||||
last_cur_ticks = cur_ticks;
|
||||
next_tick = cur_ticks + MILLISECONDS_PER_TICK;
|
||||
|
||||
bool old_ctrl_pressed = _ctrl_pressed;
|
||||
bool old_shift_pressed = _shift_pressed;
|
||||
|
||||
_ctrl_pressed = !!(_current_mods & ( _settings_client.gui.right_mouse_btn_emulation != RMBE_CONTROL ? NSControlKeyMask : NSCommandKeyMask)) != _invert_ctrl;
|
||||
_shift_pressed = !!(_current_mods & NSShiftKeyMask) != _invert_shift;
|
||||
|
||||
if (old_ctrl_pressed != _ctrl_pressed) HandleCtrlChanged();
|
||||
if (old_shift_pressed != _shift_pressed) HandleShiftChanged();
|
||||
|
||||
::GameLoop();
|
||||
::GameLoopPaletteAnimations();
|
||||
|
||||
UpdateWindows();
|
||||
QZ_CheckPaletteAnim();
|
||||
_cocoa_subdriver->Draw();
|
||||
} else {
|
||||
#ifdef _DEBUG
|
||||
uint32 st0 = GetTick();
|
||||
#endif
|
||||
CSleep(1);
|
||||
#ifdef _DEBUG
|
||||
st += GetTick() - st0;
|
||||
#endif
|
||||
NetworkDrawChatMessage();
|
||||
DrawMouseCursor();
|
||||
_cocoa_subdriver->Draw();
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef _DEBUG
|
||||
uint32 et = GetTick();
|
||||
|
||||
DEBUG(driver, 1, "cocoa_v: nextEventMatchingMask took %i ms total", _tEvent);
|
||||
DEBUG(driver, 1, "cocoa_v: game loop took %i ms total (%i ms without sleep)", et - et0, et - et0 - st);
|
||||
DEBUG(driver, 1, "cocoa_v: (nextEventMatchingMask total)/(game loop total) is %f%%", (double)_tEvent / (double)(et - et0) * 100);
|
||||
DEBUG(driver, 1, "cocoa_v: (nextEventMatchingMask total)/(game loop without sleep total) is %f%%", (double)_tEvent / (double)(et - et0 - st) * 100);
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif /* WITH_COCOA */
|
||||
@@ -21,6 +21,7 @@
|
||||
#include "../core/random_func.hpp"
|
||||
#include "../saveload/saveload.h"
|
||||
#include "../thread.h"
|
||||
#include "../window_func.h"
|
||||
#include "dedicated_v.h"
|
||||
|
||||
#ifdef __OS2__
|
||||
@@ -195,14 +196,6 @@ static bool InputWaiting()
|
||||
return select(STDIN + 1, &readfds, nullptr, nullptr, &tv) > 0;
|
||||
}
|
||||
|
||||
static uint32 GetTime()
|
||||
{
|
||||
struct timeval tim;
|
||||
|
||||
gettimeofday(&tim, nullptr);
|
||||
return tim.tv_usec / 1000 + tim.tv_sec * 1000;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
static bool InputWaiting()
|
||||
@@ -210,11 +203,6 @@ static bool InputWaiting()
|
||||
return WaitForSingleObject(_hInputReady, 1) == WAIT_OBJECT_0;
|
||||
}
|
||||
|
||||
static uint32 GetTime()
|
||||
{
|
||||
return GetTickCount();
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
static void DedicatedHandleKeyInput()
|
||||
@@ -248,8 +236,9 @@ static void DedicatedHandleKeyInput()
|
||||
|
||||
void VideoDriver_Dedicated::MainLoop()
|
||||
{
|
||||
uint32 cur_ticks = GetTime();
|
||||
uint32 next_tick = cur_ticks + MILLISECONDS_PER_TICK;
|
||||
auto cur_ticks = std::chrono::steady_clock::now();
|
||||
auto last_realtime_tick = cur_ticks;
|
||||
auto next_game_tick = cur_ticks;
|
||||
|
||||
/* Signal handlers */
|
||||
#if defined(UNIX)
|
||||
@@ -290,18 +279,31 @@ void VideoDriver_Dedicated::MainLoop()
|
||||
}
|
||||
|
||||
while (!_exit_game) {
|
||||
uint32 prev_cur_ticks = cur_ticks; // to check for wrapping
|
||||
InteractiveRandom(); // randomness
|
||||
|
||||
if (!_dedicated_forks) DedicatedHandleKeyInput();
|
||||
|
||||
cur_ticks = GetTime();
|
||||
_realtime_tick += cur_ticks - prev_cur_ticks;
|
||||
if (cur_ticks >= next_tick || cur_ticks < prev_cur_ticks || _ddc_fastforward) {
|
||||
next_tick = cur_ticks + MILLISECONDS_PER_TICK;
|
||||
cur_ticks = std::chrono::steady_clock::now();
|
||||
|
||||
/* If more than a millisecond has passed, increase the _realtime_tick. */
|
||||
if (cur_ticks - last_realtime_tick > std::chrono::milliseconds(1)) {
|
||||
auto delta = std::chrono::duration_cast<std::chrono::milliseconds>(cur_ticks - last_realtime_tick);
|
||||
IncreaseRealtimeTick(delta.count());
|
||||
last_realtime_tick += delta;
|
||||
}
|
||||
|
||||
if (cur_ticks >= next_game_tick || _ddc_fastforward) {
|
||||
if (_ddc_fastforward) {
|
||||
next_game_tick = cur_ticks + this->GetGameInterval();
|
||||
} else {
|
||||
next_game_tick += this->GetGameInterval();
|
||||
/* Avoid next_game_tick getting behind more and more if it cannot keep up. */
|
||||
if (next_game_tick < cur_ticks - ALLOWED_DRIFT * this->GetGameInterval()) next_game_tick = cur_ticks;
|
||||
}
|
||||
|
||||
GameLoop();
|
||||
GameLoopPaletteAnimations();
|
||||
InputLoop();
|
||||
UpdateWindows();
|
||||
}
|
||||
|
||||
@@ -310,9 +312,14 @@ void VideoDriver_Dedicated::MainLoop()
|
||||
/* Sleep longer on a dedicated server, if the game is paused and no clients connected.
|
||||
* That can allow the CPU to better use deep sleep states. */
|
||||
if (_pause_mode != 0 && !HasClients()) {
|
||||
CSleep(100);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
} else {
|
||||
CSleep(1);
|
||||
/* See how much time there is till we have to process the next event, and try to hit that as close as possible. */
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
|
||||
if (next_game_tick > now) {
|
||||
std::this_thread::sleep_for(next_game_tick - now);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include "../stdafx.h"
|
||||
#include "../gfx_func.h"
|
||||
#include "../blitter/factory.hpp"
|
||||
#include "../window_func.h"
|
||||
#include "null_v.h"
|
||||
|
||||
#include "../safeguards.h"
|
||||
@@ -51,12 +52,14 @@ void VideoDriver_Null::MainLoop()
|
||||
while (!_exit_game) {
|
||||
GameLoop();
|
||||
GameLoopPaletteAnimations();
|
||||
InputLoop();
|
||||
UpdateWindows();
|
||||
}
|
||||
} else {
|
||||
for (int i = 0; i < this->ticks; i++) {
|
||||
GameLoop();
|
||||
GameLoopPaletteAnimations();
|
||||
InputLoop();
|
||||
UpdateWindows();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
#include "../progress.h"
|
||||
#include "../core/random_func.hpp"
|
||||
#include "../core/math_func.hpp"
|
||||
#include "../core/mem_func.hpp"
|
||||
#include "../core/geometry_func.hpp"
|
||||
#include "../fileio_func.h"
|
||||
#include "../framerate_type.h"
|
||||
#include "../scope.h"
|
||||
@@ -69,13 +71,7 @@ static SDL_Palette *_sdl_palette;
|
||||
static bool _cursor_new_in_window = false;
|
||||
#endif
|
||||
|
||||
#define MAX_DIRTY_RECTS 100
|
||||
static SDL_Rect _dirty_rects[MAX_DIRTY_RECTS];
|
||||
static int _num_dirty_rects;
|
||||
|
||||
/* Size of window */
|
||||
static int _window_size_w;
|
||||
static int _window_size_h;
|
||||
static Rect _dirty_rect;
|
||||
|
||||
static std::string _editing_text;
|
||||
|
||||
@@ -85,6 +81,7 @@ Point GetFocusedWindowCaret();
|
||||
Point GetFocusedWindowTopLeft();
|
||||
bool FocusedWindowIsConsole();
|
||||
bool EditBoxInGlobalFocus();
|
||||
void InputLoop();
|
||||
|
||||
#if defined(WITH_FCITX)
|
||||
static bool _fcitx_mode = false;
|
||||
@@ -277,13 +274,8 @@ const static bool _suppress_text_event = false;
|
||||
|
||||
void VideoDriver_SDL::MakeDirty(int left, int top, int width, int height)
|
||||
{
|
||||
if (_num_dirty_rects < MAX_DIRTY_RECTS) {
|
||||
_dirty_rects[_num_dirty_rects].x = left;
|
||||
_dirty_rects[_num_dirty_rects].y = top;
|
||||
_dirty_rects[_num_dirty_rects].w = width;
|
||||
_dirty_rects[_num_dirty_rects].h = height;
|
||||
}
|
||||
_num_dirty_rects++;
|
||||
Rect r = {left, top, left + width, top + height};
|
||||
_dirty_rect = BoundingRect(_dirty_rect, r);
|
||||
}
|
||||
|
||||
static void UpdatePalette()
|
||||
@@ -308,6 +300,8 @@ static void MakePalette()
|
||||
if (_sdl_palette == nullptr) usererror("SDL2: Couldn't allocate palette: %s", SDL_GetError());
|
||||
}
|
||||
|
||||
_cur_palette.first_dirty = 0;
|
||||
_cur_palette.count_dirty = 256;
|
||||
_local_palette = _cur_palette;
|
||||
UpdatePalette();
|
||||
|
||||
@@ -344,11 +338,11 @@ void VideoDriver_SDL::CheckPaletteAnim()
|
||||
this->MakeDirty(0, 0, _screen.width, _screen.height);
|
||||
}
|
||||
|
||||
static void DrawSurfaceToScreen()
|
||||
static void Paint()
|
||||
{
|
||||
PerformanceMeasurer framerate(PFE_VIDEO);
|
||||
|
||||
if (_num_dirty_rects == 0) return;
|
||||
if (IsEmptyRect(_dirty_rect) && _cur_palette.count_dirty == 0) return;
|
||||
|
||||
if (_cur_palette.count_dirty != 0) {
|
||||
Blitter *blitter = BlitterFactory::GetCurrentBlitter();
|
||||
@@ -371,51 +365,17 @@ static void DrawSurfaceToScreen()
|
||||
_cur_palette.count_dirty = 0;
|
||||
}
|
||||
|
||||
int n = _num_dirty_rects;
|
||||
if (n == 0) return;
|
||||
SDL_Rect r = { _dirty_rect.left, _dirty_rect.top, _dirty_rect.right - _dirty_rect.left, _dirty_rect.bottom - _dirty_rect.top };
|
||||
|
||||
_num_dirty_rects = 0;
|
||||
|
||||
bool update_whole_screen = n > MAX_DIRTY_RECTS;
|
||||
|
||||
if (!update_whole_screen) {
|
||||
int area = 0;
|
||||
for (int i = 0; i < n; i++) {
|
||||
area += _dirty_rects[i].w * _dirty_rects[i].h;
|
||||
}
|
||||
// Rectangles cover more than 80% of screen area, just do the whole screen
|
||||
if ((area * 5) / 4 >= _sdl_surface->w * _sdl_surface->h) update_whole_screen = true;
|
||||
if (_sdl_surface != _sdl_real_surface) {
|
||||
SDL_BlitSurface(_sdl_surface, &r, _sdl_real_surface, &r);
|
||||
}
|
||||
SDL_UpdateWindowSurfaceRects(_sdl_window, &r, 1);
|
||||
|
||||
if (update_whole_screen) {
|
||||
if (_sdl_surface != _sdl_real_surface) {
|
||||
SDL_BlitSurface(_sdl_surface, nullptr, _sdl_real_surface, nullptr);
|
||||
}
|
||||
|
||||
SDL_UpdateWindowSurface(_sdl_window);
|
||||
} else {
|
||||
if (_sdl_surface != _sdl_real_surface) {
|
||||
for (int i = 0; i < _num_dirty_rects; i++) {
|
||||
SDL_BlitSurface(_sdl_surface, &_dirty_rects[i], _sdl_real_surface, &_dirty_rects[i]);
|
||||
}
|
||||
}
|
||||
SDL_Rect update_rect = { _sdl_surface->w, _sdl_surface->h, 0, 0 };
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (_dirty_rects[i].x < update_rect.x) update_rect.x = _dirty_rects[i].x;
|
||||
if (_dirty_rects[i].y < update_rect.y) update_rect.y = _dirty_rects[i].y;
|
||||
if (_dirty_rects[i].x + _dirty_rects[i].w > update_rect.w) update_rect.w = _dirty_rects[i].x + _dirty_rects[i].w;
|
||||
if (_dirty_rects[i].y + _dirty_rects[i].h > update_rect.h) update_rect.h = _dirty_rects[i].y + _dirty_rects[i].h;
|
||||
}
|
||||
update_rect.w -= update_rect.x;
|
||||
update_rect.h -= update_rect.y;
|
||||
|
||||
SDL_UpdateWindowSurfaceRects(_sdl_window, &update_rect, 1);
|
||||
}
|
||||
|
||||
_num_dirty_rects = 0;
|
||||
MemSetT(&_dirty_rect, 0);
|
||||
}
|
||||
|
||||
static void DrawSurfaceToScreenThread()
|
||||
static void PaintThread()
|
||||
{
|
||||
/* First tell the main thread we're started */
|
||||
std::unique_lock<std::recursive_mutex> lock(*_draw_mutex);
|
||||
@@ -426,7 +386,7 @@ static void DrawSurfaceToScreenThread()
|
||||
|
||||
while (_draw_continue) {
|
||||
/* Then just draw and wait till we stop */
|
||||
DrawSurfaceToScreen();
|
||||
Paint();
|
||||
_draw_signal->wait(lock);
|
||||
}
|
||||
}
|
||||
@@ -591,8 +551,12 @@ bool VideoDriver_SDL::CreateMainSurface(uint w, uint h, bool resize)
|
||||
_sdl_surface = _sdl_real_surface;
|
||||
}
|
||||
|
||||
/* Delay drawing for this cycle; the next cycle will redraw the whole screen */
|
||||
_num_dirty_rects = 0;
|
||||
/* X11 doesn't appreciate it if we invalidate areas outside the window
|
||||
* if shared memory is enabled (read: it crashes). So, as we might have
|
||||
* gotten smaller, reset our dirty rects. GameSizeChanged() a bit lower
|
||||
* will mark the whole screen dirty again anyway, but this time with the
|
||||
* new dimensions. */
|
||||
MemSetT(&_dirty_rect, 0);
|
||||
|
||||
_screen.width = _sdl_surface->w;
|
||||
_screen.height = _sdl_surface->h;
|
||||
@@ -699,7 +663,7 @@ void VideoDriver_SDL::EditBoxLostFocus()
|
||||
HandleTextInput(nullptr, true);
|
||||
}
|
||||
|
||||
struct VkMapping {
|
||||
struct SDLVkMapping {
|
||||
SDL_Keycode vk_from;
|
||||
byte vk_count;
|
||||
byte map_to;
|
||||
@@ -711,7 +675,7 @@ struct VkMapping {
|
||||
#define AS_UP(x, z) {x, 0, z, true}
|
||||
#define AM_UP(x, y, z, w) {x, (byte)(y - x), z, true}
|
||||
|
||||
static const VkMapping _vk_mapping[] = {
|
||||
static const SDLVkMapping _vk_mapping[] = {
|
||||
/* Pageup stuff + up/down */
|
||||
AS_UP(SDLK_PAGEUP, WKC_PAGEUP),
|
||||
AS_UP(SDLK_PAGEDOWN, WKC_PAGEDOWN),
|
||||
@@ -767,7 +731,7 @@ static const VkMapping _vk_mapping[] = {
|
||||
|
||||
static uint ConvertSdlKeyIntoMy(SDL_Keysym *sym, WChar *character)
|
||||
{
|
||||
const VkMapping *map;
|
||||
const SDLVkMapping *map;
|
||||
uint key = 0;
|
||||
bool unprintable = false;
|
||||
|
||||
@@ -807,7 +771,7 @@ static uint ConvertSdlKeyIntoMy(SDL_Keysym *sym, WChar *character)
|
||||
*/
|
||||
static uint ConvertSdlKeycodeIntoMy(SDL_Keycode kc)
|
||||
{
|
||||
const VkMapping *map;
|
||||
const SDLVkMapping *map;
|
||||
uint key = 0;
|
||||
|
||||
for (map = _vk_mapping; map != endof(_vk_mapping); ++map) {
|
||||
@@ -818,7 +782,7 @@ static uint ConvertSdlKeycodeIntoMy(SDL_Keycode kc)
|
||||
}
|
||||
|
||||
/* check scancode for BACKQUOTE key, because we want the key left
|
||||
of "1", not anything else (on non-US keyboards) */
|
||||
* of "1", not anything else (on non-US keyboards) */
|
||||
SDL_Scancode sc = SDL_GetScancodeFromKey(kc);
|
||||
if (sc == SDL_SCANCODE_GRAVE) key = WKC_BACKQUOTE;
|
||||
|
||||
@@ -839,10 +803,10 @@ int VideoDriver_SDL::PollEvent()
|
||||
#ifdef __EMSCRIPTEN__
|
||||
if (_cursor_new_in_window) {
|
||||
/* The cursor just moved into the window; this means we don't
|
||||
* know the absolutely position yet to move relative from.
|
||||
* Before this time, SDL didn't know it either, and this is
|
||||
* why we postpone it till now. Update the absolute position
|
||||
* for this once, and work relative after. */
|
||||
* know the absolutely position yet to move relative from.
|
||||
* Before this time, SDL didn't know it either, and this is
|
||||
* why we postpone it till now. Update the absolute position
|
||||
* for this once, and work relative after. */
|
||||
_cursor.pos.x = ev.motion.x;
|
||||
_cursor.pos.y = ev.motion.y;
|
||||
_cursor.dirty = true;
|
||||
@@ -1062,6 +1026,17 @@ const char *VideoDriver_SDL::Start(const StringList &parm)
|
||||
MarkWholeScreenDirty();
|
||||
|
||||
_draw_threaded = !GetDriverParamBool(parm, "no_threads") && !GetDriverParamBool(parm, "no_thread");
|
||||
/* Wayland SDL video driver uses EGL to render the game. SDL created the
|
||||
* EGL context from the main-thread, and with EGL you are not allowed to
|
||||
* draw in another thread than the context was created. The function of
|
||||
* _draw_threaded is to do exactly this: draw in another thread than the
|
||||
* window was created, and as such, this fails on Wayland SDL video
|
||||
* driver. So, we disable threading by default if Wayland SDL video
|
||||
* driver is detected.
|
||||
*/
|
||||
if (strcmp(dname, "wayland") == 0) {
|
||||
_draw_threaded = false;
|
||||
}
|
||||
|
||||
SDL_StopTextInput();
|
||||
this->edit_box_focused = false;
|
||||
@@ -1089,7 +1064,6 @@ void VideoDriver_SDL::LoopOnce()
|
||||
uint32 mod;
|
||||
int numkeys;
|
||||
const Uint8 *keys;
|
||||
uint32 prev_cur_ticks = cur_ticks; // to check for wrapping
|
||||
InteractiveRandom(); // randomness
|
||||
|
||||
while (PollEvent() == -1) {}
|
||||
@@ -1123,11 +1097,37 @@ void VideoDriver_SDL::LoopOnce()
|
||||
_fast_forward = 0;
|
||||
}
|
||||
|
||||
cur_ticks = SDL_GetTicks();
|
||||
if (SDL_TICKS_PASSED(cur_ticks, next_tick) || (_fast_forward && !_pause_mode) || cur_ticks < prev_cur_ticks) {
|
||||
_realtime_tick += cur_ticks - last_cur_ticks;
|
||||
last_cur_ticks = cur_ticks;
|
||||
next_tick = cur_ticks + MILLISECONDS_PER_TICK;
|
||||
cur_ticks = std::chrono::steady_clock::now();
|
||||
|
||||
/* If more than a millisecond has passed, increase the _realtime_tick. */
|
||||
if (cur_ticks - last_realtime_tick > std::chrono::milliseconds(1)) {
|
||||
auto delta = std::chrono::duration_cast<std::chrono::milliseconds>(cur_ticks - last_realtime_tick);
|
||||
IncreaseRealtimeTick(delta.count());
|
||||
last_realtime_tick += delta;
|
||||
}
|
||||
|
||||
if (cur_ticks >= next_game_tick || (_fast_forward && !_pause_mode)) {
|
||||
if (_fast_forward && !_pause_mode) {
|
||||
next_game_tick = cur_ticks + this->GetGameInterval();
|
||||
} else {
|
||||
next_game_tick += this->GetGameInterval();
|
||||
/* Avoid next_game_tick getting behind more and more if it cannot keep up. */
|
||||
if (next_game_tick < cur_ticks - ALLOWED_DRIFT * this->GetGameInterval()) next_game_tick = cur_ticks;
|
||||
}
|
||||
|
||||
/* The gameloop is the part that can run asynchronously. The rest
|
||||
* except sleeping can't. */
|
||||
if (_draw_mutex != nullptr) draw_lock.unlock();
|
||||
GameLoop();
|
||||
if (_draw_mutex != nullptr) draw_lock.lock();
|
||||
GameLoopPaletteAnimations();
|
||||
}
|
||||
|
||||
/* Prevent drawing when switching mode, as windows can be removed when they should still appear. */
|
||||
if (cur_ticks >= next_draw_tick && (_switch_mode == SM_NONE || HasModalProgress())) {
|
||||
next_draw_tick += this->GetDrawInterval();
|
||||
/* Avoid next_draw_tick getting behind more and more if it cannot keep up. */
|
||||
if (next_draw_tick < cur_ticks - ALLOWED_DRIFT * this->GetDrawInterval()) next_draw_tick = cur_ticks;
|
||||
|
||||
bool old_ctrl_pressed = _ctrl_pressed;
|
||||
bool old_shift_pressed = _shift_pressed;
|
||||
@@ -1144,50 +1144,40 @@ void VideoDriver_SDL::LoopOnce()
|
||||
if (old_ctrl_pressed != _ctrl_pressed) HandleCtrlChanged();
|
||||
if (old_shift_pressed != _shift_pressed) HandleShiftChanged();
|
||||
|
||||
/* The gameloop is the part that can run asynchronously. The rest
|
||||
* except sleeping can't. */
|
||||
if (_draw_mutex != nullptr) draw_lock.unlock();
|
||||
|
||||
GameLoop();
|
||||
|
||||
if (_draw_mutex != nullptr) draw_lock.lock();
|
||||
|
||||
GameLoopPaletteAnimations();
|
||||
|
||||
InputLoop();
|
||||
UpdateWindows();
|
||||
this->CheckPaletteAnim();
|
||||
} else {
|
||||
/* Release the thread while sleeping */
|
||||
if (_draw_mutex != nullptr) {
|
||||
draw_lock.unlock();
|
||||
CSleep(1);
|
||||
draw_lock.lock();
|
||||
|
||||
if (_draw_mutex != nullptr && !HasModalProgress()) {
|
||||
_draw_signal->notify_one();
|
||||
} else {
|
||||
Paint();
|
||||
}
|
||||
}
|
||||
|
||||
/* Emscripten is running an event-based mainloop; there is already some
|
||||
* downtime between each iteration, so no need to sleep. */
|
||||
#ifndef __EMSCRIPTEN__
|
||||
CSleep(1);
|
||||
#endif
|
||||
/* If we are not in fast-forward, create some time between calls to ease up CPU usage. */
|
||||
if (!_fast_forward || _pause_mode) {
|
||||
/* See how much time there is till we have to process the next event, and try to hit that as close as possible. */
|
||||
auto next_tick = std::min(next_draw_tick, next_game_tick);
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
|
||||
if (next_tick > now) {
|
||||
if (_draw_mutex != nullptr) draw_lock.unlock();
|
||||
std::this_thread::sleep_for(next_tick - now);
|
||||
if (_draw_mutex != nullptr) draw_lock.lock();
|
||||
}
|
||||
|
||||
NetworkDrawChatMessage();
|
||||
DrawMouseCursor();
|
||||
}
|
||||
|
||||
/* End of the critical part. */
|
||||
if (_draw_mutex != nullptr && !HasModalProgress()) {
|
||||
_draw_signal->notify_one();
|
||||
} else {
|
||||
/* Oh, we didn't have threads, then just draw unthreaded */
|
||||
DrawSurfaceToScreen();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void VideoDriver_SDL::MainLoop()
|
||||
{
|
||||
cur_ticks = SDL_GetTicks();
|
||||
last_cur_ticks = cur_ticks;
|
||||
next_tick = cur_ticks + MILLISECONDS_PER_TICK;
|
||||
cur_ticks = std::chrono::steady_clock::now();
|
||||
last_realtime_tick = cur_ticks;
|
||||
next_game_tick = cur_ticks;
|
||||
|
||||
this->CheckPaletteAnim();
|
||||
|
||||
@@ -1202,7 +1192,7 @@ void VideoDriver_SDL::MainLoop()
|
||||
_draw_signal = new std::condition_variable_any();
|
||||
_draw_continue = true;
|
||||
|
||||
_draw_threaded = StartNewThread(&draw_thread, "ottd:draw-sdl", &DrawSurfaceToScreenThread);
|
||||
_draw_threaded = StartNewThread(&draw_thread, "ottd:draw-sdl", &PaintThread);
|
||||
|
||||
/* Free the mutex if we won't be able to use it. */
|
||||
if (!_draw_threaded) {
|
||||
@@ -1274,9 +1264,11 @@ bool VideoDriver_SDL::ToggleFullscreen(bool fullscreen)
|
||||
std::unique_lock<std::recursive_mutex> lock;
|
||||
if (_draw_mutex != nullptr) lock = std::unique_lock<std::recursive_mutex>(*_draw_mutex);
|
||||
|
||||
int w, h;
|
||||
|
||||
/* Remember current window size */
|
||||
if (fullscreen) {
|
||||
SDL_GetWindowSize(_sdl_window, &_window_size_w, &_window_size_h);
|
||||
SDL_GetWindowSize(_sdl_window, &w, &h);
|
||||
|
||||
/* Find fullscreen window size */
|
||||
SDL_DisplayMode dm;
|
||||
@@ -1292,7 +1284,7 @@ bool VideoDriver_SDL::ToggleFullscreen(bool fullscreen)
|
||||
if (ret == 0) {
|
||||
/* Switching resolution succeeded, set fullscreen value of window. */
|
||||
_fullscreen = fullscreen;
|
||||
if (!fullscreen) SDL_SetWindowSize(_sdl_window, _window_size_w, _window_size_h);
|
||||
if (!fullscreen) SDL_SetWindowSize(_sdl_window, w, h);
|
||||
} else {
|
||||
DEBUG(driver, 0, "SDL_SetWindowFullscreen() failed: %s", SDL_GetError());
|
||||
}
|
||||
|
||||
@@ -62,9 +62,10 @@ private:
|
||||
*/
|
||||
bool edit_box_focused;
|
||||
|
||||
uint32 cur_ticks;
|
||||
uint32 last_cur_ticks;
|
||||
uint32 next_tick;
|
||||
std::chrono::steady_clock::time_point cur_ticks;
|
||||
std::chrono::steady_clock::time_point last_realtime_tick;
|
||||
std::chrono::steady_clock::time_point next_game_tick;
|
||||
std::chrono::steady_clock::time_point next_draw_tick;
|
||||
|
||||
int startup_display;
|
||||
std::thread draw_thread;
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#include "../core/math_func.hpp"
|
||||
#include "../fileio_func.h"
|
||||
#include "../framerate_type.h"
|
||||
#include "../window_func.h"
|
||||
#include "sdl_v.h"
|
||||
#include <SDL.h>
|
||||
#include <mutex>
|
||||
@@ -408,7 +409,7 @@ bool VideoDriver_SDL::ClaimMousePointer()
|
||||
return true;
|
||||
}
|
||||
|
||||
struct VkMapping {
|
||||
struct SDLVkMapping {
|
||||
#if SDL_VERSION_ATLEAST(1, 3, 0)
|
||||
SDL_Keycode vk_from;
|
||||
#else
|
||||
@@ -421,7 +422,7 @@ struct VkMapping {
|
||||
#define AS(x, z) {x, 0, z}
|
||||
#define AM(x, y, z, w) {x, (byte)(y - x), z}
|
||||
|
||||
static const VkMapping _vk_mapping[] = {
|
||||
static const SDLVkMapping _vk_mapping[] = {
|
||||
/* Pageup stuff + up/down */
|
||||
AM(SDLK_PAGEUP, SDLK_PAGEDOWN, WKC_PAGEUP, WKC_PAGEDOWN),
|
||||
AS(SDLK_UP, WKC_UP),
|
||||
@@ -476,7 +477,7 @@ static const VkMapping _vk_mapping[] = {
|
||||
|
||||
static uint ConvertSdlKeyIntoMy(SDL_keysym *sym, WChar *character)
|
||||
{
|
||||
const VkMapping *map;
|
||||
const SDLVkMapping *map;
|
||||
uint key = 0;
|
||||
|
||||
for (map = _vk_mapping; map != endof(_vk_mapping); ++map) {
|
||||
@@ -654,9 +655,10 @@ void VideoDriver_SDL::Stop()
|
||||
|
||||
void VideoDriver_SDL::MainLoop()
|
||||
{
|
||||
uint32 cur_ticks = SDL_GetTicks();
|
||||
uint32 last_cur_ticks = cur_ticks;
|
||||
uint32 next_tick = cur_ticks + MILLISECONDS_PER_TICK;
|
||||
auto cur_ticks = std::chrono::steady_clock::now();
|
||||
auto last_realtime_tick = cur_ticks;
|
||||
auto next_game_tick = cur_ticks;
|
||||
auto next_draw_tick = cur_ticks;
|
||||
uint32 mod;
|
||||
int numkeys;
|
||||
Uint8 *keys;
|
||||
@@ -696,7 +698,6 @@ void VideoDriver_SDL::MainLoop()
|
||||
DEBUG(driver, 1, "SDL: using %sthreads", _draw_threaded ? "" : "no ");
|
||||
|
||||
for (;;) {
|
||||
uint32 prev_cur_ticks = cur_ticks; // to check for wrapping
|
||||
InteractiveRandom(); // randomness
|
||||
|
||||
while (PollEvent() == -1) {}
|
||||
@@ -725,11 +726,37 @@ void VideoDriver_SDL::MainLoop()
|
||||
_fast_forward = 0;
|
||||
}
|
||||
|
||||
cur_ticks = SDL_GetTicks();
|
||||
if (cur_ticks >= next_tick || (_fast_forward && !_pause_mode) || cur_ticks < prev_cur_ticks) {
|
||||
_realtime_tick += cur_ticks - last_cur_ticks;
|
||||
last_cur_ticks = cur_ticks;
|
||||
next_tick = cur_ticks + MILLISECONDS_PER_TICK;
|
||||
cur_ticks = std::chrono::steady_clock::now();
|
||||
|
||||
/* If more than a millisecond has passed, increase the _realtime_tick. */
|
||||
if (cur_ticks - last_realtime_tick > std::chrono::milliseconds(1)) {
|
||||
auto delta = std::chrono::duration_cast<std::chrono::milliseconds>(cur_ticks - last_realtime_tick);
|
||||
IncreaseRealtimeTick(delta.count());
|
||||
last_realtime_tick += delta;
|
||||
}
|
||||
|
||||
if (cur_ticks >= next_game_tick || (_fast_forward && !_pause_mode)) {
|
||||
if (_fast_forward && !_pause_mode) {
|
||||
next_game_tick = cur_ticks + this->GetGameInterval();
|
||||
} else {
|
||||
next_game_tick += this->GetGameInterval();
|
||||
/* Avoid next_game_tick getting behind more and more if it cannot keep up. */
|
||||
if (next_game_tick < cur_ticks - ALLOWED_DRIFT * this->GetGameInterval()) next_game_tick = cur_ticks;
|
||||
}
|
||||
|
||||
/* The gameloop is the part that can run asynchronously. The rest
|
||||
* except sleeping can't. */
|
||||
if (_draw_mutex != nullptr) draw_lock.unlock();
|
||||
GameLoop();
|
||||
if (_draw_mutex != nullptr) draw_lock.lock();
|
||||
GameLoopPaletteAnimations();
|
||||
}
|
||||
|
||||
/* Prevent drawing when switching mode, as windows can be removed when they should still appear. */
|
||||
if (cur_ticks >= next_draw_tick && (_switch_mode == SM_NONE || HasModalProgress())) {
|
||||
next_draw_tick += this->GetDrawInterval();
|
||||
/* Avoid next_draw_tick getting behind more and more if it cannot keep up. */
|
||||
if (next_draw_tick < cur_ticks - ALLOWED_DRIFT * this->GetDrawInterval()) next_draw_tick = cur_ticks;
|
||||
|
||||
bool old_ctrl_pressed = _ctrl_pressed;
|
||||
bool old_shift_pressed = _shift_pressed;
|
||||
@@ -753,35 +780,29 @@ void VideoDriver_SDL::MainLoop()
|
||||
if (old_ctrl_pressed != _ctrl_pressed) HandleCtrlChanged();
|
||||
if (old_shift_pressed != _shift_pressed) HandleShiftChanged();
|
||||
|
||||
/* The gameloop is the part that can run asynchronously. The rest
|
||||
* except sleeping can't. */
|
||||
if (_draw_mutex != nullptr) draw_lock.unlock();
|
||||
|
||||
GameLoop();
|
||||
|
||||
if (_draw_mutex != nullptr) draw_lock.lock();
|
||||
|
||||
GameLoopPaletteAnimations();
|
||||
|
||||
InputLoop();
|
||||
UpdateWindows();
|
||||
_local_palette = _cur_palette;
|
||||
} else {
|
||||
/* Release the thread while sleeping */
|
||||
if (_draw_mutex != nullptr) draw_lock.unlock();
|
||||
CSleep(1);
|
||||
if (_draw_mutex != nullptr) draw_lock.lock();
|
||||
|
||||
NetworkDrawChatMessage();
|
||||
DrawMouseCursor();
|
||||
if (_draw_mutex != nullptr && !HasModalProgress()) {
|
||||
_draw_signal->notify_one();
|
||||
} else {
|
||||
CheckPaletteAnim();
|
||||
DrawSurfaceToScreen();
|
||||
}
|
||||
}
|
||||
|
||||
/* End of the critical part. */
|
||||
if (_draw_mutex != nullptr && !HasModalProgress()) {
|
||||
_draw_signal->notify_one();
|
||||
} else {
|
||||
/* Oh, we didn't have threads, then just draw unthreaded */
|
||||
CheckPaletteAnim();
|
||||
DrawSurfaceToScreen();
|
||||
/* If we are not in fast-forward, create some time between calls to ease up CPU usage. */
|
||||
if (!_fast_forward || _pause_mode) {
|
||||
/* See how much time there is till we have to process the next event, and try to hit that as close as possible. */
|
||||
auto next_tick = std::min(next_draw_tick, next_game_tick);
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
|
||||
if (next_tick > now) {
|
||||
if (_draw_mutex != nullptr) draw_lock.unlock();
|
||||
std::this_thread::sleep_for(next_tick - now);
|
||||
if (_draw_mutex != nullptr) draw_lock.lock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,9 @@
|
||||
#include "../driver.h"
|
||||
#include "../core/geometry_type.hpp"
|
||||
#include "../core/math_func.hpp"
|
||||
#include "../settings_type.h"
|
||||
#include "../zoom_type.h"
|
||||
#include <chrono>
|
||||
#include <vector>
|
||||
|
||||
extern std::string _ini_videodriver;
|
||||
@@ -105,6 +108,18 @@ public:
|
||||
*/
|
||||
virtual void EditBoxLostFocus() {}
|
||||
|
||||
/**
|
||||
* Get a suggested default GUI zoom taking screen DPI into account.
|
||||
*/
|
||||
virtual ZoomLevel GetSuggestedUIZoom()
|
||||
{
|
||||
float dpi_scale = this->GetDPIScale();
|
||||
|
||||
if (dpi_scale >= 3.0f) return ZOOM_LVL_NORMAL;
|
||||
if (dpi_scale >= 1.5f) return ZOOM_LVL_OUT_2X;
|
||||
return ZOOM_LVL_OUT_4X;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the currently active instance of the video driver.
|
||||
*/
|
||||
@@ -113,11 +128,19 @@ public:
|
||||
}
|
||||
|
||||
protected:
|
||||
/*
|
||||
const uint ALLOWED_DRIFT = 5; ///< How many times videodriver can miss deadlines without it being overly compensated.
|
||||
|
||||
/**
|
||||
* Get the resolution of the main screen.
|
||||
*/
|
||||
virtual Dimension GetScreenSize() const { return { DEFAULT_WINDOW_WIDTH, DEFAULT_WINDOW_HEIGHT }; }
|
||||
|
||||
/**
|
||||
* Get DPI scaling factor of the screen OTTD is displayed on.
|
||||
* @return 1.0 for default platform DPI, > 1.0 for higher DPI values, and < 1.0 for smaller DPI values.
|
||||
*/
|
||||
virtual float GetDPIScale() { return 1.0f; }
|
||||
|
||||
/**
|
||||
* Apply resolution auto-detection and clamp to sensible defaults.
|
||||
*/
|
||||
@@ -132,6 +155,16 @@ protected:
|
||||
_cur_resolution.height = ClampU(res.height * 3 / 4, DEFAULT_WINDOW_HEIGHT, UINT16_MAX / 2);
|
||||
}
|
||||
}
|
||||
|
||||
std::chrono::steady_clock::duration GetGameInterval()
|
||||
{
|
||||
return std::chrono::milliseconds(MILLISECONDS_PER_TICK);
|
||||
}
|
||||
|
||||
std::chrono::steady_clock::duration GetDrawInterval()
|
||||
{
|
||||
return std::chrono::microseconds(1000000 / _settings_client.gui.refresh_rate);
|
||||
}
|
||||
};
|
||||
|
||||
#endif /* VIDEO_VIDEO_DRIVER_HPP */
|
||||
|
||||
@@ -119,7 +119,7 @@ bool VideoDriver_Win32::ClaimMousePointer()
|
||||
return true;
|
||||
}
|
||||
|
||||
struct VkMapping {
|
||||
struct Win32VkMapping {
|
||||
byte vk_from;
|
||||
byte vk_count;
|
||||
byte map_to;
|
||||
@@ -128,7 +128,7 @@ struct VkMapping {
|
||||
#define AS(x, z) {x, 0, z}
|
||||
#define AM(x, y, z, w) {x, y - x, z}
|
||||
|
||||
static const VkMapping _vk_mapping[] = {
|
||||
static const Win32VkMapping _vk_mapping[] = {
|
||||
/* Pageup stuff + up/down */
|
||||
AM(VK_PRIOR, VK_DOWN, WKC_PAGEUP, WKC_DOWN),
|
||||
/* Map letters & digits */
|
||||
@@ -171,7 +171,7 @@ static const VkMapping _vk_mapping[] = {
|
||||
|
||||
static uint MapWindowsKey(uint sym)
|
||||
{
|
||||
const VkMapping *map;
|
||||
const Win32VkMapping *map;
|
||||
uint key = 0;
|
||||
|
||||
for (map = _vk_mapping; map != endof(_vk_mapping); ++map) {
|
||||
@@ -1138,9 +1138,10 @@ static void CheckPaletteAnim()
|
||||
void VideoDriver_Win32::MainLoop()
|
||||
{
|
||||
MSG mesg;
|
||||
uint32 cur_ticks = GetTickCount();
|
||||
uint32 last_cur_ticks = cur_ticks;
|
||||
uint32 next_tick = cur_ticks + MILLISECONDS_PER_TICK;
|
||||
auto cur_ticks = std::chrono::steady_clock::now();
|
||||
auto last_realtime_tick = cur_ticks;
|
||||
auto next_game_tick = cur_ticks;
|
||||
auto next_draw_tick = cur_ticks;
|
||||
|
||||
std::thread draw_thread;
|
||||
std::unique_lock<std::recursive_mutex> draw_lock;
|
||||
@@ -1181,8 +1182,6 @@ void VideoDriver_Win32::MainLoop()
|
||||
|
||||
CheckPaletteAnim();
|
||||
for (;;) {
|
||||
uint32 prev_cur_ticks = cur_ticks; // to check for wrapping
|
||||
|
||||
while (PeekMessage(&mesg, nullptr, 0, 0, PM_REMOVE)) {
|
||||
InteractiveRandom(); // randomness
|
||||
/* Convert key messages to char messages if we want text input. */
|
||||
@@ -1203,11 +1202,40 @@ void VideoDriver_Win32::MainLoop()
|
||||
_fast_forward = 0;
|
||||
}
|
||||
|
||||
cur_ticks = GetTickCount();
|
||||
if (cur_ticks >= next_tick || (_fast_forward && !_pause_mode) || cur_ticks < prev_cur_ticks) {
|
||||
_realtime_tick += cur_ticks - last_cur_ticks;
|
||||
last_cur_ticks = cur_ticks;
|
||||
next_tick = cur_ticks + MILLISECONDS_PER_TICK;
|
||||
cur_ticks = std::chrono::steady_clock::now();
|
||||
|
||||
/* If more than a millisecond has passed, increase the _realtime_tick. */
|
||||
if (cur_ticks - last_realtime_tick > std::chrono::milliseconds(1)) {
|
||||
auto delta = std::chrono::duration_cast<std::chrono::milliseconds>(cur_ticks - last_realtime_tick);
|
||||
IncreaseRealtimeTick(delta.count());
|
||||
last_realtime_tick += delta;
|
||||
}
|
||||
|
||||
if (cur_ticks >= next_game_tick || (_fast_forward && !_pause_mode)) {
|
||||
if (_fast_forward && !_pause_mode) {
|
||||
next_game_tick = cur_ticks + this->GetGameInterval();
|
||||
} else {
|
||||
next_game_tick += this->GetGameInterval();
|
||||
/* Avoid next_game_tick getting behind more and more if it cannot keep up. */
|
||||
if (next_game_tick < cur_ticks - ALLOWED_DRIFT * this->GetGameInterval()) next_game_tick = cur_ticks;
|
||||
}
|
||||
|
||||
/* Flush GDI buffer to ensure we don't conflict with the drawing thread. */
|
||||
GdiFlush();
|
||||
|
||||
/* The game loop is the part that can run asynchronously.
|
||||
* The rest except sleeping can't. */
|
||||
if (_draw_threaded) draw_lock.unlock();
|
||||
GameLoop();
|
||||
if (_draw_threaded) draw_lock.lock();
|
||||
GameLoopPaletteAnimations();
|
||||
}
|
||||
|
||||
/* Prevent drawing when switching mode, as windows can be removed when they should still appear. */
|
||||
if (cur_ticks >= next_draw_tick && (_switch_mode == SM_NONE || HasModalProgress())) {
|
||||
next_draw_tick += this->GetDrawInterval();
|
||||
/* Avoid next_draw_tick getting behind more and more if it cannot keep up. */
|
||||
if (next_draw_tick < cur_ticks - ALLOWED_DRIFT * this->GetDrawInterval()) next_draw_tick = cur_ticks;
|
||||
|
||||
bool old_ctrl_pressed = _ctrl_pressed;
|
||||
bool old_shift_pressed = _shift_pressed;
|
||||
@@ -1229,31 +1257,30 @@ void VideoDriver_Win32::MainLoop()
|
||||
if (old_ctrl_pressed != _ctrl_pressed) HandleCtrlChanged();
|
||||
if (old_shift_pressed != _shift_pressed) HandleShiftChanged();
|
||||
|
||||
/* Flush GDI buffer to ensure we don't conflict with the drawing thread. */
|
||||
GdiFlush();
|
||||
|
||||
/* The game loop is the part that can run asynchronously.
|
||||
* The rest except sleeping can't. */
|
||||
if (_draw_threaded) draw_lock.unlock();
|
||||
GameLoop();
|
||||
if (_draw_threaded) draw_lock.lock();
|
||||
GameLoopPaletteAnimations();
|
||||
|
||||
if (_force_full_redraw) MarkWholeScreenDirty();
|
||||
|
||||
UpdateWindows();
|
||||
CheckPaletteAnim();
|
||||
} else {
|
||||
/* Flush GDI buffer to ensure we don't conflict with the drawing thread. */
|
||||
GdiFlush();
|
||||
|
||||
/* Release the thread while sleeping */
|
||||
if (_draw_threaded) draw_lock.unlock();
|
||||
CSleep(1);
|
||||
if (_draw_threaded) draw_lock.lock();
|
||||
InputLoop();
|
||||
UpdateWindows();
|
||||
CheckPaletteAnim();
|
||||
}
|
||||
|
||||
NetworkDrawChatMessage();
|
||||
DrawMouseCursor();
|
||||
/* If we are not in fast-forward, create some time between calls to ease up CPU usage. */
|
||||
if (!_fast_forward || _pause_mode) {
|
||||
/* See how much time there is till we have to process the next event, and try to hit that as close as possible. */
|
||||
auto next_tick = std::min(next_draw_tick, next_game_tick);
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
|
||||
if (next_tick > now) {
|
||||
/* Flush GDI buffer to ensure we don't conflict with the drawing thread. */
|
||||
GdiFlush();
|
||||
|
||||
if (_draw_mutex != nullptr) draw_lock.unlock();
|
||||
std::this_thread::sleep_for(next_tick - now);
|
||||
if (_draw_mutex != nullptr) draw_lock.lock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1324,3 +1351,43 @@ Dimension VideoDriver_Win32::GetScreenSize() const
|
||||
{
|
||||
return { static_cast<uint>(GetSystemMetrics(SM_CXSCREEN)), static_cast<uint>(GetSystemMetrics(SM_CYSCREEN)) };
|
||||
}
|
||||
|
||||
float VideoDriver_Win32::GetDPIScale()
|
||||
{
|
||||
typedef UINT (WINAPI *PFNGETDPIFORWINDOW)(HWND hwnd);
|
||||
typedef UINT (WINAPI *PFNGETDPIFORSYSTEM)(VOID);
|
||||
typedef HRESULT (WINAPI *PFNGETDPIFORMONITOR)(HMONITOR hMonitor, int dpiType, UINT *dpiX, UINT *dpiY);
|
||||
|
||||
static PFNGETDPIFORWINDOW _GetDpiForWindow = nullptr;
|
||||
static PFNGETDPIFORSYSTEM _GetDpiForSystem = nullptr;
|
||||
static PFNGETDPIFORMONITOR _GetDpiForMonitor = nullptr;
|
||||
|
||||
static bool init_done = false;
|
||||
if (!init_done) {
|
||||
init_done = true;
|
||||
|
||||
_GetDpiForWindow = (PFNGETDPIFORWINDOW)GetProcAddress(GetModuleHandle(_T("User32")), "GetDpiForWindow");
|
||||
_GetDpiForSystem = (PFNGETDPIFORSYSTEM)GetProcAddress(GetModuleHandle(_T("User32")), "GetDpiForSystem");
|
||||
_GetDpiForMonitor = (PFNGETDPIFORMONITOR)GetProcAddress(LoadLibrary(_T("Shcore.dll")), "GetDpiForMonitor");
|
||||
}
|
||||
|
||||
UINT cur_dpi = 0;
|
||||
|
||||
if (cur_dpi == 0 && _GetDpiForWindow != nullptr && _wnd.main_wnd != nullptr) {
|
||||
/* Per window DPI is supported since Windows 10 Ver 1607. */
|
||||
cur_dpi = _GetDpiForWindow(_wnd.main_wnd);
|
||||
}
|
||||
if (cur_dpi == 0 && _GetDpiForMonitor != nullptr && _wnd.main_wnd != nullptr) {
|
||||
/* Per monitor is supported since Windows 8.1. */
|
||||
UINT dpiX, dpiY;
|
||||
if (SUCCEEDED(_GetDpiForMonitor(MonitorFromWindow(_wnd.main_wnd, MONITOR_DEFAULTTOPRIMARY), 0 /* MDT_EFFECTIVE_DPI */, &dpiX, &dpiY))) {
|
||||
cur_dpi = dpiX; // X and Y are always identical.
|
||||
}
|
||||
}
|
||||
if (cur_dpi == 0 && _GetDpiForSystem != nullptr) {
|
||||
/* Fall back to system DPI. */
|
||||
cur_dpi = _GetDpiForSystem();
|
||||
}
|
||||
|
||||
return cur_dpi > 0 ? cur_dpi / 96.0f : 1.0f; // Default Windows DPI value is 96.
|
||||
}
|
||||
|
||||
@@ -43,6 +43,8 @@ public:
|
||||
|
||||
protected:
|
||||
Dimension GetScreenSize() const override;
|
||||
|
||||
float GetDPIScale() override;
|
||||
};
|
||||
|
||||
/** The factory for Windows' video driver. */
|
||||
|
||||
Reference in New Issue
Block a user