main.mm (13395B)
1 // Dear ImGui: standalone example application for OSX + Metal. 2 // If you are new to Dear ImGui, read documentation from the docs/ folder + read the top of imgui.cpp. 3 // Read online: https://github.com/ocornut/imgui/tree/master/docs 4 5 #import <Foundation/Foundation.h> 6 7 #if TARGET_OS_OSX 8 #import <Cocoa/Cocoa.h> 9 #else 10 #import <UIKit/UIKit.h> 11 #endif 12 13 #import <Metal/Metal.h> 14 #import <MetalKit/MetalKit.h> 15 16 #include "imgui.h" 17 #include "imgui_impl_metal.h" 18 19 #if TARGET_OS_OSX 20 #include "imgui_impl_osx.h" 21 22 @interface ViewController : NSViewController 23 @end 24 #else 25 @interface ViewController : UIViewController 26 @end 27 #endif 28 29 @interface ViewController () <MTKViewDelegate> 30 @property (nonatomic, readonly) MTKView *mtkView; 31 @property (nonatomic, strong) id <MTLDevice> device; 32 @property (nonatomic, strong) id <MTLCommandQueue> commandQueue; 33 @end 34 35 @implementation ViewController 36 37 - (instancetype)initWithNibName:(nullable NSString *)nibNameOrNil bundle:(nullable NSBundle *)nibBundleOrNil 38 { 39 self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; 40 41 _device = MTLCreateSystemDefaultDevice(); 42 _commandQueue = [_device newCommandQueue]; 43 44 if (!self.device) 45 { 46 NSLog(@"Metal is not supported"); 47 abort(); 48 } 49 50 // Setup Dear ImGui context 51 // FIXME: This example doesn't have proper cleanup... 52 IMGUI_CHECKVERSION(); 53 ImGui::CreateContext(); 54 ImGuiIO& io = ImGui::GetIO(); (void)io; 55 //io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls 56 //io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls 57 58 // Setup Dear ImGui style 59 ImGui::StyleColorsDark(); 60 //ImGui::StyleColorsClassic(); 61 62 // Setup Renderer backend 63 ImGui_ImplMetal_Init(_device); 64 65 // Load Fonts 66 // - If no fonts are loaded, dear imgui will use the default font. You can also load multiple fonts and use ImGui::PushFont()/PopFont() to select them. 67 // - AddFontFromFileTTF() will return the ImFont* so you can store it if you need to select the font among multiple. 68 // - If the file cannot be loaded, the function will return NULL. Please handle those errors in your application (e.g. use an assertion, or display an error and quit). 69 // - The fonts will be rasterized at a given size (w/ oversampling) and stored into a texture when calling ImFontAtlas::Build()/GetTexDataAsXXXX(), which ImGui_ImplXXXX_NewFrame below will call. 70 // - Read 'docs/FONTS.txt' for more instructions and details. 71 // - Remember that in C/C++ if you want to include a backslash \ in a string literal you need to write a double backslash \\ ! 72 //io.Fonts->AddFontDefault(); 73 //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Roboto-Medium.ttf", 16.0f); 74 //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Cousine-Regular.ttf", 15.0f); 75 //io.Fonts->AddFontFromFileTTF("../../misc/fonts/DroidSans.ttf", 16.0f); 76 //io.Fonts->AddFontFromFileTTF("../../misc/fonts/ProggyTiny.ttf", 10.0f); 77 //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 18.0f, NULL, io.Fonts->GetGlyphRangesJapanese()); 78 //IM_ASSERT(font != NULL); 79 80 return self; 81 } 82 83 - (MTKView *)mtkView 84 { 85 return (MTKView *)self.view; 86 } 87 88 - (void)loadView 89 { 90 self.view = [[MTKView alloc] initWithFrame:CGRectMake(0, 0, 1200, 720)]; 91 } 92 93 - (void)viewDidLoad 94 { 95 [super viewDidLoad]; 96 97 self.mtkView.device = self.device; 98 self.mtkView.delegate = self; 99 100 #if TARGET_OS_OSX 101 // Add a tracking area in order to receive mouse events whenever the mouse is within the bounds of our view 102 NSTrackingArea *trackingArea = [[NSTrackingArea alloc] initWithRect:NSZeroRect 103 options:NSTrackingMouseMoved | NSTrackingInVisibleRect | NSTrackingActiveAlways 104 owner:self 105 userInfo:nil]; 106 [self.view addTrackingArea:trackingArea]; 107 108 // If we want to receive key events, we either need to be in the responder chain of the key view, 109 // or else we can install a local monitor. The consequence of this heavy-handed approach is that 110 // we receive events for all controls, not just Dear ImGui widgets. If we had native controls in our 111 // window, we'd want to be much more careful than just ingesting the complete event stream. 112 // To match the behavior of other backends, we pass every event down to the OS. 113 NSEventMask eventMask = NSEventMaskKeyDown | NSEventMaskKeyUp | NSEventMaskFlagsChanged | NSEventTypeScrollWheel; 114 [NSEvent addLocalMonitorForEventsMatchingMask:eventMask handler:^NSEvent * _Nullable(NSEvent *event) 115 { 116 ImGui_ImplOSX_HandleEvent(event, self.view); 117 return event; 118 }]; 119 120 ImGui_ImplOSX_Init(); 121 122 #endif 123 } 124 125 #pragma mark - Interaction 126 127 #if TARGET_OS_OSX 128 129 - (void)mouseMoved:(NSEvent *)event { 130 ImGui_ImplOSX_HandleEvent(event, self.view); 131 } 132 133 - (void)mouseDown:(NSEvent *)event { 134 ImGui_ImplOSX_HandleEvent(event, self.view); 135 } 136 137 - (void)rightMouseDown:(NSEvent *)event { 138 ImGui_ImplOSX_HandleEvent(event, self.view); 139 } 140 141 - (void)otherMouseDown:(NSEvent *)event { 142 ImGui_ImplOSX_HandleEvent(event, self.view); 143 } 144 145 - (void)mouseUp:(NSEvent *)event { 146 ImGui_ImplOSX_HandleEvent(event, self.view); 147 } 148 149 - (void)rightMouseUp:(NSEvent *)event { 150 ImGui_ImplOSX_HandleEvent(event, self.view); 151 } 152 153 - (void)otherMouseUp:(NSEvent *)event { 154 ImGui_ImplOSX_HandleEvent(event, self.view); 155 } 156 157 - (void)mouseDragged:(NSEvent *)event { 158 ImGui_ImplOSX_HandleEvent(event, self.view); 159 } 160 161 - (void)rightMouseDragged:(NSEvent *)event { 162 ImGui_ImplOSX_HandleEvent(event, self.view); 163 } 164 165 - (void)otherMouseDragged:(NSEvent *)event { 166 ImGui_ImplOSX_HandleEvent(event, self.view); 167 } 168 169 - (void)scrollWheel:(NSEvent *)event { 170 ImGui_ImplOSX_HandleEvent(event, self.view); 171 } 172 173 #else 174 175 // This touch mapping is super cheesy/hacky. We treat any touch on the screen 176 // as if it were a depressed left mouse button, and we don't bother handling 177 // multitouch correctly at all. This causes the "cursor" to behave very erratically 178 // when there are multiple active touches. But for demo purposes, single-touch 179 // interaction actually works surprisingly well. 180 - (void)updateIOWithTouchEvent:(UIEvent *)event 181 { 182 UITouch *anyTouch = event.allTouches.anyObject; 183 CGPoint touchLocation = [anyTouch locationInView:self.view]; 184 ImGuiIO &io = ImGui::GetIO(); 185 io.MousePos = ImVec2(touchLocation.x, touchLocation.y); 186 187 BOOL hasActiveTouch = NO; 188 for (UITouch *touch in event.allTouches) 189 { 190 if (touch.phase != UITouchPhaseEnded && touch.phase != UITouchPhaseCancelled) 191 { 192 hasActiveTouch = YES; 193 break; 194 } 195 } 196 io.MouseDown[0] = hasActiveTouch; 197 } 198 199 - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event 200 { 201 [self updateIOWithTouchEvent:event]; 202 } 203 204 - (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event 205 { 206 [self updateIOWithTouchEvent:event]; 207 } 208 209 - (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event 210 { 211 [self updateIOWithTouchEvent:event]; 212 } 213 214 - (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event 215 { 216 [self updateIOWithTouchEvent:event]; 217 } 218 219 #endif 220 221 #pragma mark - MTKViewDelegate 222 223 - (void)drawInMTKView:(MTKView*)view 224 { 225 ImGuiIO& io = ImGui::GetIO(); 226 io.DisplaySize.x = view.bounds.size.width; 227 io.DisplaySize.y = view.bounds.size.height; 228 229 #if TARGET_OS_OSX 230 CGFloat framebufferScale = view.window.screen.backingScaleFactor ?: NSScreen.mainScreen.backingScaleFactor; 231 #else 232 CGFloat framebufferScale = view.window.screen.scale ?: UIScreen.mainScreen.scale; 233 #endif 234 io.DisplayFramebufferScale = ImVec2(framebufferScale, framebufferScale); 235 236 io.DeltaTime = 1 / float(view.preferredFramesPerSecond ?: 60); 237 238 id<MTLCommandBuffer> commandBuffer = [self.commandQueue commandBuffer]; 239 240 // Our state (make them static = more or less global) as a convenience to keep the example terse. 241 static bool show_demo_window = true; 242 static bool show_another_window = false; 243 static float clear_color[4] = { 0.28f, 0.36f, 0.5f, 1.0f }; 244 245 MTLRenderPassDescriptor* renderPassDescriptor = view.currentRenderPassDescriptor; 246 if (renderPassDescriptor != nil) 247 { 248 renderPassDescriptor.colorAttachments[0].clearColor = MTLClearColorMake(clear_color[0] * clear_color[3], clear_color[1] * clear_color[3], clear_color[2] * clear_color[3], clear_color[3]); 249 250 // Here, you could do additional rendering work, including other passes as necessary. 251 252 id <MTLRenderCommandEncoder> renderEncoder = [commandBuffer renderCommandEncoderWithDescriptor:renderPassDescriptor]; 253 [renderEncoder pushDebugGroup:@"ImGui demo"]; 254 255 // Start the Dear ImGui frame 256 ImGui_ImplMetal_NewFrame(renderPassDescriptor); 257 #if TARGET_OS_OSX 258 ImGui_ImplOSX_NewFrame(view); 259 #endif 260 ImGui::NewFrame(); 261 262 // 1. Show the big demo window (Most of the sample code is in ImGui::ShowDemoWindow()! You can browse its code to learn more about Dear ImGui!). 263 if (show_demo_window) 264 ImGui::ShowDemoWindow(&show_demo_window); 265 266 // 2. Show a simple window that we create ourselves. We use a Begin/End pair to created a named window. 267 { 268 static float f = 0.0f; 269 static int counter = 0; 270 271 ImGui::Begin("Hello, world!"); // Create a window called "Hello, world!" and append into it. 272 273 ImGui::Text("This is some useful text."); // Display some text (you can use a format strings too) 274 ImGui::Checkbox("Demo Window", &show_demo_window); // Edit bools storing our window open/close state 275 ImGui::Checkbox("Another Window", &show_another_window); 276 277 ImGui::SliderFloat("float", &f, 0.0f, 1.0f); // Edit 1 float using a slider from 0.0f to 1.0f 278 ImGui::ColorEdit3("clear color", (float*)&clear_color); // Edit 3 floats representing a color 279 280 if (ImGui::Button("Button")) // Buttons return true when clicked (most widgets return true when edited/activated) 281 counter++; 282 ImGui::SameLine(); 283 ImGui::Text("counter = %d", counter); 284 285 ImGui::Text("Application average %.3f ms/frame (%.1f FPS)", 1000.0f / ImGui::GetIO().Framerate, ImGui::GetIO().Framerate); 286 ImGui::End(); 287 } 288 289 // 3. Show another simple window. 290 if (show_another_window) 291 { 292 ImGui::Begin("Another Window", &show_another_window); // Pass a pointer to our bool variable (the window will have a closing button that will clear the bool when clicked) 293 ImGui::Text("Hello from another window!"); 294 if (ImGui::Button("Close Me")) 295 show_another_window = false; 296 ImGui::End(); 297 } 298 299 // Rendering 300 ImGui::Render(); 301 ImDrawData* draw_data = ImGui::GetDrawData(); 302 ImGui_ImplMetal_RenderDrawData(draw_data, commandBuffer, renderEncoder); 303 304 [renderEncoder popDebugGroup]; 305 [renderEncoder endEncoding]; 306 307 [commandBuffer presentDrawable:view.currentDrawable]; 308 } 309 310 [commandBuffer commit]; 311 } 312 313 - (void)mtkView:(MTKView*)view drawableSizeWillChange:(CGSize)size 314 { 315 } 316 317 @end 318 319 #pragma mark - Application Delegate 320 321 #if TARGET_OS_OSX 322 323 @interface AppDelegate : NSObject <NSApplicationDelegate> 324 @property (nonatomic, strong) NSWindow *window; 325 @end 326 327 @implementation AppDelegate 328 329 - (instancetype)init 330 { 331 if (self = [super init]) 332 { 333 NSViewController *rootViewController = [[ViewController alloc] initWithNibName:nil bundle:nil]; 334 self.window = [[NSWindow alloc] initWithContentRect:NSZeroRect 335 styleMask:NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskResizable | NSWindowStyleMaskMiniaturizable 336 backing:NSBackingStoreBuffered 337 defer:NO]; 338 self.window.contentViewController = rootViewController; 339 [self.window orderFront:self]; 340 [self.window center]; 341 [self.window becomeKeyWindow]; 342 } 343 return self; 344 } 345 346 - (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)sender 347 { 348 return YES; 349 } 350 351 @end 352 353 #else 354 355 @interface AppDelegate : UIResponder <UIApplicationDelegate> 356 @property (strong, nonatomic) UIWindow *window; 357 @end 358 359 @implementation AppDelegate 360 361 - (BOOL)application:(UIApplication *)application 362 didFinishLaunchingWithOptions:(NSDictionary<UIApplicationLaunchOptionsKey,id> *)launchOptions 363 { 364 UIViewController *rootViewController = [[ViewController alloc] init]; 365 self.window = [[UIWindow alloc] initWithFrame:UIScreen.mainScreen.bounds]; 366 self.window.rootViewController = rootViewController; 367 [self.window makeKeyAndVisible]; 368 return YES; 369 } 370 371 @end 372 373 #endif 374 375 #pragma mark - main() 376 377 #if TARGET_OS_OSX 378 379 int main(int argc, const char * argv[]) 380 { 381 return NSApplicationMain(argc, argv); 382 } 383 384 #else 385 386 int main(int argc, char * argv[]) 387 { 388 @autoreleasepool 389 { 390 return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 391 } 392 } 393 394 #endif