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:
Jonathan G Rennison
2021-02-19 15:38:34 +00:00
176 changed files with 4275 additions and 3826 deletions

View File

@@ -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);
}
}
}
}

View File

@@ -4,6 +4,5 @@ add_files(
cocoa_v.mm
cocoa_wnd.h
cocoa_wnd.mm
event.mm
CONDITION APPLE
)

View File

@@ -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

View File

@@ -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 &param) 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 &param) 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

View File

@@ -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

View File

@@ -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 */

View File

@@ -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 */

View File

@@ -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);
}
}
}
}

View File

@@ -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();
}
}

View File

@@ -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());
}

View File

@@ -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;

View File

@@ -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();
}
}
}

View File

@@ -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 */

View File

@@ -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.
}

View File

@@ -43,6 +43,8 @@ public:
protected:
Dimension GetScreenSize() const override;
float GetDPIScale() override;
};
/** The factory for Windows' video driver. */