yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
ios_window_backend.mm
Go to the documentation of this file.
2
3#if defined(__APPLE__)
4#include <TargetConditionals.h>
5#endif
6
7#if defined(__APPLE__) && (TARGET_OS_IPHONE == 1 || TARGET_IPHONE_SIMULATOR == 1)
8#import <CoreFoundation/CoreFoundation.h>
9#import <Metal/Metal.h>
10#import <MetalKit/MetalKit.h>
11#import <UIKit/UIKit.h>
12#endif
13
14#include <algorithm>
15
17#include "app/gui/core/style.h"
20#include "imgui/backends/imgui_impl_metal.h"
21#include "imgui/imgui.h"
22#include "util/log.h"
23
24namespace yaze {
25namespace platform {
26
27namespace {
28#if defined(__APPLE__) && (TARGET_OS_IPHONE == 1 || TARGET_IPHONE_SIMULATOR == 1)
29UIEdgeInsets GetSafeAreaInsets(MTKView* view) {
30 if (!view) {
31 return UIEdgeInsetsZero;
32 }
33 if (@available(iOS 11.0, *)) {
34 return view.safeAreaInsets;
35 }
36 return UIEdgeInsetsZero;
37}
38
39void ApplyTouchStyle(MTKView* view) {
40 ImGuiStyle& style = ImGui::GetStyle();
41 const float frame_height = ImGui::GetFrameHeight();
42 const float target_height = std::max(44.0f, frame_height);
43 const float touch_extra =
44 std::clamp((target_height - frame_height) * 0.5f, 0.0f, 16.0f);
45 style.TouchExtraPadding = ImVec2(touch_extra, touch_extra);
46
47 const float font_size = ImGui::GetFontSize();
48 if (font_size > 0.0f) {
49 style.ScrollbarSize = std::max(style.ScrollbarSize, font_size * 1.1f);
50 style.GrabMinSize = std::max(style.GrabMinSize, font_size * 0.9f);
51 style.FramePadding.x = std::max(style.FramePadding.x, font_size * 0.55f);
52 style.FramePadding.y = std::max(style.FramePadding.y, font_size * 0.35f);
53 style.ItemSpacing.x = std::max(style.ItemSpacing.x, font_size * 0.45f);
54 style.ItemSpacing.y = std::max(style.ItemSpacing.y, font_size * 0.35f);
55 }
56
57 const UIEdgeInsets insets = GetSafeAreaInsets(view);
58 const float safe_x = std::max(insets.left, insets.right);
59 const float safe_y = std::max(insets.top, insets.bottom);
60 style.DisplaySafeAreaPadding = ImVec2(safe_x, safe_y);
61 ios::SetSafeAreaInsets(insets.left, insets.right, insets.top,
62 insets.bottom);
63}
64#endif
65} // namespace
66
67absl::Status IOSWindowBackend::Initialize(const WindowConfig& config) {
68#if defined(__APPLE__) && (TARGET_OS_IPHONE == 1 || TARGET_IPHONE_SIMULATOR == 1)
70 if (!metal_view_) {
71 return absl::FailedPreconditionError("Metal view not set");
72 }
73
74 title_ = config.title;
75 status_.is_active = true;
76 status_.is_focused = true;
78
79 auto* view = static_cast<MTKView*>(metal_view_);
80 status_.width = static_cast<int>(view.bounds.size.width);
81 status_.height = static_cast<int>(view.bounds.size.height);
82
83 initialized_ = true;
84 return absl::OkStatus();
85#else
86 (void)config;
87 return absl::FailedPreconditionError(
88 "IOSWindowBackend is only available on iOS");
89#endif
90}
91
94
95#if defined(__APPLE__) && (TARGET_OS_IPHONE == 1 || TARGET_IPHONE_SIMULATOR == 1)
96 if (command_queue_) {
97 CFRelease(command_queue_);
98 command_queue_ = nullptr;
99 }
100#endif
101
102 metal_view_ = nullptr;
103 initialized_ = false;
104 return absl::OkStatus();
105}
106
108 return initialized_;
109}
110
112 out_event = WindowEvent{};
113 return false;
114}
115
116void IOSWindowBackend::ProcessNativeEvent(void* native_event) {
117 (void)native_event;
118}
119
121 WindowStatus status = status_;
122#if defined(__APPLE__) && (TARGET_OS_IPHONE == 1 || TARGET_IPHONE_SIMULATOR == 1)
123 if (metal_view_) {
124 auto* view = static_cast<MTKView*>(metal_view_);
125 status.width = static_cast<int>(view.bounds.size.width);
126 status.height = static_cast<int>(view.bounds.size.height);
127 }
128#endif
129 return status;
130}
131
133 return status_.is_active;
134}
135
137 status_.is_active = active;
138}
139
140void IOSWindowBackend::GetSize(int* width, int* height) const {
141#if defined(__APPLE__) && (TARGET_OS_IPHONE == 1 || TARGET_IPHONE_SIMULATOR == 1)
142 if (metal_view_) {
143 auto* view = static_cast<MTKView*>(metal_view_);
144 if (width) {
145 *width = static_cast<int>(view.bounds.size.width);
146 }
147 if (height) {
148 *height = static_cast<int>(view.bounds.size.height);
149 }
150 return;
151 }
152#endif
153
154 if (width) {
155 *width = 0;
156 }
157 if (height) {
158 *height = 0;
159 }
160}
161
162void IOSWindowBackend::SetSize(int width, int height) {
163#if defined(__APPLE__) && (TARGET_OS_IPHONE == 1 || TARGET_IPHONE_SIMULATOR == 1)
164 if (metal_view_) {
165 auto* view = static_cast<MTKView*>(metal_view_);
166 view.drawableSize = CGSizeMake(width, height);
167 }
168#else
169 (void)width;
170 (void)height;
171#endif
172}
173
174std::string IOSWindowBackend::GetTitle() const {
175 return title_;
176}
177
178void IOSWindowBackend::SetTitle(const std::string& title) {
179 title_ = title;
180}
181
183 if (!renderer || !metal_view_) {
184 return false;
185 }
186
187 if (renderer->GetBackendRenderer()) {
188 return true;
189 }
190
191 auto* metal_renderer = dynamic_cast<gfx::MetalRenderer*>(renderer);
192 if (metal_renderer) {
193 metal_renderer->SetMetalView(metal_view_);
194 } else {
195 LOG_WARN("IOSWindowBackend", "Non-Metal renderer selected on iOS");
196 }
197
198 return renderer->Initialize(nullptr);
199}
200
202 return nullptr;
203}
204
206 if (imgui_initialized_) {
207 return absl::OkStatus();
208 }
209
210 if (!renderer) {
211 return absl::InvalidArgumentError("Renderer is null");
212 }
213
214#if defined(__APPLE__) && (TARGET_OS_IPHONE == 1 || TARGET_IPHONE_SIMULATOR == 1)
215 if (!metal_view_) {
216 return absl::FailedPreconditionError("Metal view not set");
217 }
218
219 auto* view = static_cast<MTKView*>(metal_view_);
220 id<MTLDevice> device = view.device;
221 if (!device) {
222 device = MTLCreateSystemDefaultDevice();
223 view.device = device;
224 }
225
226 if (!device) {
227 return absl::InternalError("Failed to create Metal device");
228 }
229
230 IMGUI_CHECKVERSION();
231 ImGui::CreateContext();
232
233 ImGuiIO& io = ImGui::GetIO();
234 io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
235 io.ConfigFlags |= ImGuiConfigFlags_DockingEnable;
236 io.ConfigFlags |= ImGuiConfigFlags_IsTouchScreen;
237
238 if (!ImGui_ImplMetal_Init(device)) {
239 return absl::InternalError("ImGui_ImplMetal_Init failed");
240 }
241
242 auto font_status = LoadPackageFonts();
243 if (!font_status.ok()) {
244 ImGui_ImplMetal_Shutdown();
245 ImGui::DestroyContext();
246 return font_status;
247 }
249#if defined(__APPLE__) && (TARGET_OS_IPHONE == 1 || TARGET_IPHONE_SIMULATOR == 1)
250 ApplyTouchStyle(view);
251#endif
252
253 if (!command_queue_) {
254 id<MTLCommandQueue> queue = [device newCommandQueue];
255 command_queue_ = (__bridge_retained void*)queue;
256 }
257
258 imgui_initialized_ = true;
259 LOG_INFO("IOSWindowBackend", "ImGui initialized with Metal backend");
260 return absl::OkStatus();
261#else
262 return absl::FailedPreconditionError(
263 "IOSWindowBackend is only available on iOS");
264#endif
265}
266
268 if (!imgui_initialized_) {
269 return;
270 }
271
272 ImGui_ImplMetal_Shutdown();
273 ImGui::DestroyContext();
274
275 imgui_initialized_ = false;
276}
277
279 if (!imgui_initialized_) {
280 return;
281 }
282
283#if defined(__APPLE__) && (TARGET_OS_IPHONE == 1 || TARGET_IPHONE_SIMULATOR == 1)
284 auto* view = static_cast<MTKView*>(metal_view_);
285 if (!view) {
286 return;
287 }
288
289 ApplyTouchStyle(view);
290
291 auto* render_pass = view.currentRenderPassDescriptor;
292 if (!render_pass) {
293 return;
294 }
295
296 ImGui_ImplMetal_NewFrame(render_pass);
297#endif
298}
299
301 if (!imgui_initialized_) {
302 return;
303 }
304
305 ImGui::Render();
306
307#if defined(__APPLE__) && (TARGET_OS_IPHONE == 1 || TARGET_IPHONE_SIMULATOR == 1)
308 (void)renderer;
309 auto* view = static_cast<MTKView*>(metal_view_);
310 if (!view || !view.currentDrawable) {
311 return;
312 }
313
314 auto* render_pass = view.currentRenderPassDescriptor;
315 if (!render_pass || !command_queue_) {
316 return;
317 }
318
319 id<MTLCommandQueue> queue =
320 (__bridge id<MTLCommandQueue>)command_queue_;
321 id<MTLCommandBuffer> command_buffer = [queue commandBuffer];
322 id<MTLRenderCommandEncoder> encoder =
323 [command_buffer renderCommandEncoderWithDescriptor:render_pass];
324
325 ImGui_ImplMetal_RenderDrawData(ImGui::GetDrawData(), command_buffer, encoder);
326 [encoder endEncoding];
327 [command_buffer presentDrawable:view.currentDrawable];
328 [command_buffer commit];
329#else
330 (void)renderer;
331#endif
332}
333
335 return 0;
336}
337
338std::shared_ptr<int16_t> IOSWindowBackend::GetAudioBuffer() const {
339 return nullptr;
340}
341
343 return "iOS-Metal";
344}
345
347 return 0;
348}
349
350} // namespace platform
351} // namespace yaze
Defines an abstract interface for all rendering operations.
Definition irenderer.h:40
virtual bool Initialize(SDL_Window *window)=0
Initializes the renderer with a given window.
virtual void * GetBackendRenderer()=0
Provides an escape hatch to get the underlying, concrete renderer object.
void SetMetalView(void *view)
bool IsInitialized() const override
Check if the backend is initialized.
void ProcessNativeEvent(void *native_event) override
Process a native SDL event (for ImGui integration)
std::shared_ptr< int16_t > GetAudioBuffer() const override
Get audio buffer (for legacy audio management)
absl::Status InitializeImGui(gfx::IRenderer *renderer) override
Initialize ImGui backends for this window/renderer combo.
std::string GetBackendName() const override
Get backend name for debugging/logging.
bool PollEvent(WindowEvent &out_event) override
Poll and process pending events.
absl::Status Initialize(const WindowConfig &config) override
Initialize the window backend with configuration.
bool IsActive() const override
Check if window is still active (not closed)
void RenderImGui(gfx::IRenderer *renderer) override
Render ImGui draw data (and viewports if enabled)
bool InitializeRenderer(gfx::IRenderer *renderer) override
Initialize renderer for this window.
void SetTitle(const std::string &title) override
Set window title.
uint32_t GetAudioDevice() const override
Get audio device ID (for legacy audio buffer management)
SDL_Window * GetNativeWindow() override
Get the underlying SDL_Window pointer for ImGui integration.
std::string GetTitle() const override
Get window title.
absl::Status Shutdown() override
Shutdown the window backend and release resources.
void SetActive(bool active) override
Set window active state.
void GetSize(int *width, int *height) const override
Get window dimensions.
void ShutdownImGui() override
Shutdown ImGui backends.
void SetSize(int width, int height) override
Set window dimensions.
void NewImGuiFrame() override
Start a new ImGui frame.
int GetSDLVersion() const override
Get SDL version being used.
WindowStatus GetStatus() const override
Get current window status.
#define LOG_WARN(category, format,...)
Definition log.h:107
#define LOG_INFO(category, format,...)
Definition log.h:105
void ColorsYaze()
Definition style.cc:32
void SetSafeAreaInsets(float left, float right, float top, float bottom)
absl::Status LoadPackageFonts()
Window configuration parameters.
Definition iwindow.h:24
Platform-agnostic window event data.
Definition iwindow.h:63
Window backend status information.
Definition iwindow.h:96