4#include <TargetConditionals.h>
7#if defined(__APPLE__) && (TARGET_OS_IPHONE == 1 || TARGET_IPHONE_SIMULATOR == 1)
8#import <CoreFoundation/CoreFoundation.h>
10#import <MetalKit/MetalKit.h>
11#import <UIKit/UIKit.h>
20#include "imgui/backends/imgui_impl_metal.h"
21#include "imgui/imgui.h"
28#if defined(__APPLE__) && (TARGET_OS_IPHONE == 1 || TARGET_IPHONE_SIMULATOR == 1)
29UIEdgeInsets GetSafeAreaInsets(MTKView* view) {
31 return UIEdgeInsetsZero;
33 if (@available(iOS 11.0, *)) {
34 return view.safeAreaInsets;
36 return UIEdgeInsetsZero;
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);
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);
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);
68#if defined(__APPLE__) && (TARGET_OS_IPHONE == 1 || TARGET_IPHONE_SIMULATOR == 1)
71 return absl::FailedPreconditionError(
"Metal view not set");
80 status_.
width =
static_cast<int>(view.bounds.size.width);
84 return absl::OkStatus();
87 return absl::FailedPreconditionError(
88 "IOSWindowBackend is only available on iOS");
95#if defined(__APPLE__) && (TARGET_OS_IPHONE == 1 || TARGET_IPHONE_SIMULATOR == 1)
104 return absl::OkStatus();
122#if defined(__APPLE__) && (TARGET_OS_IPHONE == 1 || TARGET_IPHONE_SIMULATOR == 1)
125 status.
width =
static_cast<int>(view.bounds.size.width);
126 status.
height =
static_cast<int>(view.bounds.size.height);
141#if defined(__APPLE__) && (TARGET_OS_IPHONE == 1 || TARGET_IPHONE_SIMULATOR == 1)
145 *width =
static_cast<int>(view.bounds.size.width);
148 *height =
static_cast<int>(view.bounds.size.height);
163#if defined(__APPLE__) && (TARGET_OS_IPHONE == 1 || TARGET_IPHONE_SIMULATOR == 1)
166 view.drawableSize = CGSizeMake(width, height);
192 if (metal_renderer) {
195 LOG_WARN(
"IOSWindowBackend",
"Non-Metal renderer selected on iOS");
207 return absl::OkStatus();
211 return absl::InvalidArgumentError(
"Renderer is null");
214#if defined(__APPLE__) && (TARGET_OS_IPHONE == 1 || TARGET_IPHONE_SIMULATOR == 1)
216 return absl::FailedPreconditionError(
"Metal view not set");
220 id<MTLDevice> device = view.device;
222 device = MTLCreateSystemDefaultDevice();
223 view.device = device;
227 return absl::InternalError(
"Failed to create Metal device");
230 IMGUI_CHECKVERSION();
231 ImGui::CreateContext();
233 ImGuiIO& io = ImGui::GetIO();
234 io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
235 io.ConfigFlags |= ImGuiConfigFlags_DockingEnable;
236 io.ConfigFlags |= ImGuiConfigFlags_IsTouchScreen;
238 if (!ImGui_ImplMetal_Init(device)) {
239 return absl::InternalError(
"ImGui_ImplMetal_Init failed");
243 if (!font_status.ok()) {
244 ImGui_ImplMetal_Shutdown();
245 ImGui::DestroyContext();
249#if defined(__APPLE__) && (TARGET_OS_IPHONE == 1 || TARGET_IPHONE_SIMULATOR == 1)
250 ApplyTouchStyle(view);
254 id<MTLCommandQueue> queue = [device newCommandQueue];
259 LOG_INFO(
"IOSWindowBackend",
"ImGui initialized with Metal backend");
260 return absl::OkStatus();
262 return absl::FailedPreconditionError(
263 "IOSWindowBackend is only available on iOS");
272 ImGui_ImplMetal_Shutdown();
273 ImGui::DestroyContext();
283#if defined(__APPLE__) && (TARGET_OS_IPHONE == 1 || TARGET_IPHONE_SIMULATOR == 1)
289 ApplyTouchStyle(view);
291 auto* render_pass = view.currentRenderPassDescriptor;
296 ImGui_ImplMetal_NewFrame(render_pass);
307#if defined(__APPLE__) && (TARGET_OS_IPHONE == 1 || TARGET_IPHONE_SIMULATOR == 1)
310 if (!view || !view.currentDrawable) {
314 auto* render_pass = view.currentRenderPassDescriptor;
319 id<MTLCommandQueue> queue =
321 id<MTLCommandBuffer> command_buffer = [queue commandBuffer];
322 id<MTLRenderCommandEncoder> encoder =
323 [command_buffer renderCommandEncoderWithDescriptor:render_pass];
325 ImGui_ImplMetal_RenderDrawData(ImGui::GetDrawData(), command_buffer, encoder);
326 [encoder endEncoding];
327 [command_buffer presentDrawable:view.currentDrawable];
328 [command_buffer commit];
Defines an abstract interface for all rendering operations.
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.
#define LOG_WARN(category, format,...)
#define LOG_INFO(category, format,...)
absl::Status LoadPackageFonts()