main.cpp 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580
  1. /*
  2. * Copyright (c) 2021, Jesse Buhagiar <jooster669@gmail.com>
  3. * Copyright (c) 2022, the SerenityOS developers.
  4. *
  5. * SPDX-License-Identifier: BSD-2-Clause
  6. */
  7. #include <LibCore/ArgsParser.h>
  8. #include <LibCore/ElapsedTimer.h>
  9. #include <LibCore/System.h>
  10. #include <LibFileSystemAccessClient/Client.h>
  11. #include <LibGL/GL/gl.h>
  12. #include <LibGL/GLContext.h>
  13. #include <LibGUI/ActionGroup.h>
  14. #include <LibGUI/Application.h>
  15. #include <LibGUI/FilePicker.h>
  16. #include <LibGUI/Icon.h>
  17. #include <LibGUI/Label.h>
  18. #include <LibGUI/Menu.h>
  19. #include <LibGUI/Menubar.h>
  20. #include <LibGUI/MessageBox.h>
  21. #include <LibGUI/Painter.h>
  22. #include <LibGUI/Widget.h>
  23. #include <LibGUI/Window.h>
  24. #include <LibGfx/Bitmap.h>
  25. #include <LibGfx/Palette.h>
  26. #include <LibMain/Main.h>
  27. #include "Mesh.h"
  28. #include "WavefrontOBJLoader.h"
  29. class GLContextWidget final : public GUI::Frame {
  30. C_OBJECT(GLContextWidget);
  31. public:
  32. bool load_file(String const& filename, NonnullOwnPtr<Core::File> file);
  33. void toggle_rotate_x() { m_rotate_x = !m_rotate_x; }
  34. void toggle_rotate_y() { m_rotate_y = !m_rotate_y; }
  35. void toggle_rotate_z() { m_rotate_z = !m_rotate_z; }
  36. void set_rotation_speed(float speed) { m_rotation_speed = speed; }
  37. void set_stat_label(RefPtr<GUI::Label> l) { m_stats = l; }
  38. void set_wrap_s_mode(GLint mode) { m_wrap_s_mode = mode; }
  39. void set_wrap_t_mode(GLint mode) { m_wrap_t_mode = mode; }
  40. void set_texture_scale(float scale) { m_texture_scale = scale; }
  41. void set_texture_enabled(bool texture_enabled) { m_texture_enabled = texture_enabled; }
  42. void set_mag_filter(GLint filter) { m_mag_filter = filter; }
  43. void toggle_show_frame_rate()
  44. {
  45. m_show_frame_rate = !m_show_frame_rate;
  46. m_stats->set_visible(m_show_frame_rate);
  47. }
  48. private:
  49. GLContextWidget()
  50. : m_mesh_loader(adopt_own(*new WavefrontOBJLoader()))
  51. {
  52. constexpr u16 RENDER_WIDTH = 640;
  53. constexpr u16 RENDER_HEIGHT = 480;
  54. m_bitmap = Gfx::Bitmap::create(Gfx::BitmapFormat::BGRx8888, { RENDER_WIDTH, RENDER_HEIGHT }).release_value_but_fixme_should_propagate_errors();
  55. m_context = MUST(GL::create_context(*m_bitmap));
  56. start_timer(20);
  57. GL::make_context_current(m_context);
  58. glFrontFace(GL_CCW);
  59. glEnable(GL_CULL_FACE);
  60. glEnable(GL_DEPTH_TEST);
  61. // Enable lighting
  62. glEnable(GL_LIGHTING);
  63. glEnable(GL_LIGHT0);
  64. glEnable(GL_LIGHT1);
  65. glEnable(GL_LIGHT2);
  66. // Set projection matrix
  67. glMatrixMode(GL_PROJECTION);
  68. glLoadIdentity();
  69. auto const half_aspect_ratio = static_cast<double>(RENDER_WIDTH) / RENDER_HEIGHT / 2;
  70. glFrustum(-half_aspect_ratio, half_aspect_ratio, -0.5, 0.5, 1, 1500);
  71. m_init_list = glGenLists(1);
  72. glNewList(m_init_list, GL_COMPILE);
  73. {
  74. glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
  75. glClearDepth(1.0);
  76. glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  77. }
  78. glEndList();
  79. }
  80. virtual void drag_enter_event(GUI::DragEvent&) override;
  81. virtual void drop_event(GUI::DropEvent&) override;
  82. virtual void paint_event(GUI::PaintEvent&) override;
  83. virtual void resize_event(GUI::ResizeEvent&) override;
  84. virtual void timer_event(Core::TimerEvent&) override;
  85. virtual void mousemove_event(GUI::MouseEvent&) override;
  86. virtual void mousewheel_event(GUI::MouseEvent&) override;
  87. virtual void keydown_event(GUI::KeyEvent&) override;
  88. private:
  89. RefPtr<Mesh> m_mesh;
  90. RefPtr<Gfx::Bitmap> m_bitmap;
  91. OwnPtr<GL::GLContext> m_context;
  92. NonnullOwnPtr<WavefrontOBJLoader> m_mesh_loader;
  93. GLuint m_init_list { 0 };
  94. bool m_rotate_x = true;
  95. bool m_rotate_y = false;
  96. bool m_rotate_z = true;
  97. float m_angle_x = 0.0;
  98. float m_angle_y = 0.0;
  99. float m_angle_z = 0.0;
  100. Gfx::IntPoint m_last_mouse;
  101. float m_rotation_speed = 60.f;
  102. bool m_show_frame_rate = false;
  103. int m_cycles = 0;
  104. Duration m_accumulated_time = {};
  105. RefPtr<GUI::Label> m_stats;
  106. GLint m_wrap_s_mode = GL_REPEAT;
  107. GLint m_wrap_t_mode = GL_REPEAT;
  108. bool m_texture_enabled { true };
  109. float m_texture_scale = 1.0f;
  110. GLint m_mag_filter = GL_NEAREST;
  111. float m_zoom = 1;
  112. };
  113. void GLContextWidget::drag_enter_event(GUI::DragEvent& event)
  114. {
  115. auto const& mime_types = event.mime_types();
  116. if (mime_types.contains_slow("text/uri-list"))
  117. event.accept();
  118. }
  119. void GLContextWidget::drop_event(GUI::DropEvent& event)
  120. {
  121. if (!event.mime_data().has_urls())
  122. return;
  123. event.accept();
  124. if (event.mime_data().urls().is_empty())
  125. return;
  126. for (auto& url : event.mime_data().urls()) {
  127. if (url.scheme() != "file")
  128. continue;
  129. auto response = FileSystemAccessClient::Client::the().request_file_read_only_approved(window(), url.serialize_path());
  130. if (response.is_error())
  131. return;
  132. load_file(response.value().filename(), response.value().release_stream());
  133. }
  134. }
  135. void GLContextWidget::paint_event(GUI::PaintEvent& event)
  136. {
  137. GUI::Frame::paint_event(event);
  138. GUI::Painter painter(*this);
  139. painter.add_clip_rect(event.rect());
  140. painter.draw_scaled_bitmap(frame_inner_rect(), *m_bitmap, m_bitmap->rect());
  141. }
  142. void GLContextWidget::resize_event(GUI::ResizeEvent& event)
  143. {
  144. GUI::Frame::resize_event(event);
  145. if (m_stats)
  146. m_stats->set_x(width() - m_stats->width() - 6);
  147. }
  148. void GLContextWidget::mousemove_event(GUI::MouseEvent& event)
  149. {
  150. if (event.buttons() == GUI::MouseButton::Primary) {
  151. int delta_x = m_last_mouse.x() - event.x();
  152. int delta_y = m_last_mouse.y() - event.y();
  153. m_angle_x -= delta_y / 2.0f;
  154. m_angle_y -= delta_x / 2.0f;
  155. }
  156. m_last_mouse = event.position();
  157. }
  158. void GLContextWidget::mousewheel_event(GUI::MouseEvent& event)
  159. {
  160. if (event.wheel_delta_y() > 0)
  161. m_zoom /= 1.1f;
  162. else
  163. m_zoom *= 1.1f;
  164. }
  165. void GLContextWidget::keydown_event(GUI::KeyEvent& event)
  166. {
  167. if (event.key() == Key_Escape && window()->is_fullscreen()) {
  168. window()->set_fullscreen(false);
  169. return;
  170. }
  171. event.ignore();
  172. }
  173. void GLContextWidget::timer_event(Core::TimerEvent&)
  174. {
  175. auto timer = Core::ElapsedTimer::start_new();
  176. static unsigned int light_counter = 0;
  177. glCallList(m_init_list);
  178. if (m_rotate_x)
  179. m_angle_x -= m_rotation_speed * 0.01f;
  180. if (m_rotate_y)
  181. m_angle_y -= m_rotation_speed * 0.01f;
  182. if (m_rotate_z)
  183. m_angle_z -= m_rotation_speed * 0.01f;
  184. glMatrixMode(GL_MODELVIEW);
  185. glLoadIdentity();
  186. glTranslatef(0, 0, -8.5);
  187. glRotatef(m_angle_x, 1, 0, 0);
  188. glRotatef(m_angle_y, 0, 1, 0);
  189. glRotatef(m_angle_z, 0, 0, 1);
  190. glPushMatrix();
  191. glLoadIdentity();
  192. // Disco time ;)
  193. GLfloat const light0_position[4] = { -4.0f, 0.0f, 0.0f, 0.0f };
  194. GLfloat const light0_diffuse[4] = { 1.0f, 0.0f, 0.0f, 0.0f };
  195. GLfloat const light0_specular[4] = { 0.75f, 0.75f, 0.75f };
  196. GLfloat const light1_position[4] = { 4.0f, 0.0f, 0.0f, 0.0f };
  197. GLfloat const light1_diffuse[4] = { 0.0f, 1.0f, 0.0f, 0.0f };
  198. GLfloat const light1_specular[4] = { 0.75f, 0.75f, 0.75f };
  199. GLfloat const light2_position[4] = { 0.0f, 5.0f, 0.0f, 0.0f };
  200. GLfloat const light2_diffuse[4] = { 0.0f, 0.0f, 1.0f, 0.0f };
  201. GLfloat const light2_specular[4] = { 0.75f, 0.75f, 0.75f };
  202. glLightfv(GL_LIGHT0, GL_POSITION, &light0_position[0]);
  203. glLightfv(GL_LIGHT0, GL_DIFFUSE, &light0_diffuse[0]);
  204. glLightfv(GL_LIGHT0, GL_SPECULAR, &light0_specular[0]);
  205. glLightfv(GL_LIGHT1, GL_POSITION, &light1_position[0]);
  206. glLightfv(GL_LIGHT1, GL_DIFFUSE, &light1_diffuse[0]);
  207. glLightfv(GL_LIGHT1, GL_SPECULAR, &light1_specular[0]);
  208. glLightfv(GL_LIGHT2, GL_POSITION, &light2_position[0]);
  209. glLightfv(GL_LIGHT2, GL_DIFFUSE, &light2_diffuse[0]);
  210. glLightfv(GL_LIGHT2, GL_SPECULAR, &light2_specular[0]);
  211. GLfloat const material_specular_color[4] = { 1.0f, 1.0f, 1.0f, 1.0f };
  212. glMaterialf(GL_FRONT, GL_SHININESS, 45.0f);
  213. glMaterialfv(GL_FRONT, GL_SPECULAR, &material_specular_color[0]);
  214. glPopMatrix();
  215. if (m_texture_enabled) {
  216. glEnable(GL_TEXTURE_2D);
  217. glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, m_wrap_s_mode);
  218. glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, m_wrap_t_mode);
  219. glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, m_mag_filter);
  220. } else {
  221. glDisable(GL_TEXTURE_2D);
  222. }
  223. glScalef(m_zoom, m_zoom, m_zoom);
  224. if (!m_mesh.is_null())
  225. m_mesh->draw(m_texture_scale);
  226. m_context->present();
  227. if ((m_cycles % 30) == 0) {
  228. auto render_time = static_cast<double>(m_accumulated_time.to_milliseconds()) / 30.0;
  229. auto frame_rate = render_time > 0 ? 1000 / render_time : 0;
  230. m_stats->set_text(String::formatted("{:.0f} fps, {:.1f} ms", frame_rate, render_time).release_value_but_fixme_should_propagate_errors());
  231. m_accumulated_time = {};
  232. glEnable(GL_LIGHT0);
  233. glEnable(GL_LIGHT1);
  234. glEnable(GL_LIGHT2);
  235. light_counter++;
  236. if ((light_counter % 3) == 0)
  237. glDisable(GL_LIGHT0);
  238. else if ((light_counter % 3) == 1)
  239. glDisable(GL_LIGHT1);
  240. else
  241. glDisable(GL_LIGHT2);
  242. }
  243. update();
  244. m_accumulated_time += timer.elapsed_time();
  245. m_cycles++;
  246. }
  247. bool GLContextWidget::load_file(String const& filename, NonnullOwnPtr<Core::File> file)
  248. {
  249. if (!filename.bytes_as_string_view().ends_with(".obj"sv)) {
  250. GUI::MessageBox::show(window(), DeprecatedString::formatted("Opening \"{}\" failed: invalid file type", filename), "Error"sv, GUI::MessageBox::Type::Error);
  251. return false;
  252. }
  253. auto new_mesh = m_mesh_loader->load(filename, move(file));
  254. if (new_mesh.is_error()) {
  255. GUI::MessageBox::show(window(), DeprecatedString::formatted("Reading \"{}\" failed: {}", filename, new_mesh.release_error()), "Error"sv, GUI::MessageBox::Type::Error);
  256. return false;
  257. }
  258. // Determine whether or not a texture for this model resides within the same directory
  259. StringBuilder builder;
  260. builder.append(filename.bytes_as_string_view().split_view('.').at(0));
  261. builder.append(".bmp"sv);
  262. // Attempt to open the texture file from disk
  263. RefPtr<Gfx::Bitmap> texture_image;
  264. auto response = FileSystemAccessClient::Client::the().request_file_read_only_approved(window(), builder.string_view());
  265. if (!response.is_error()) {
  266. auto texture_file = response.release_value();
  267. auto bitmap_or_error = Gfx::Bitmap::load_from_file(texture_file.release_stream(), texture_file.filename());
  268. if (!bitmap_or_error.is_error())
  269. texture_image = bitmap_or_error.release_value_but_fixme_should_propagate_errors();
  270. }
  271. GLuint tex;
  272. glGenTextures(1, &tex);
  273. if (texture_image) {
  274. // Upload texture data to the GL
  275. glBindTexture(GL_TEXTURE_2D, tex);
  276. glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, texture_image->width(), texture_image->height(), 0, GL_BGRA, GL_UNSIGNED_BYTE, texture_image->scanline(0));
  277. } else {
  278. dbgln("3DFileViewer: Couldn't load texture for {}", filename);
  279. }
  280. m_mesh = new_mesh.release_value();
  281. dbgln("3DFileViewer: mesh has {} triangles.", m_mesh->triangle_count());
  282. window()->set_title(DeprecatedString::formatted("{} - 3D File Viewer", filename));
  283. return true;
  284. }
  285. ErrorOr<int> serenity_main(Main::Arguments arguments)
  286. {
  287. auto app = TRY(GUI::Application::create(arguments));
  288. StringView filename;
  289. Core::ArgsParser args_parser;
  290. args_parser.add_positional_argument(filename, "3D model file to open", "path", Core::ArgsParser::Required::No);
  291. args_parser.parse(arguments);
  292. if (filename.is_empty())
  293. filename = "/home/anon/Documents/3D Models/teapot.obj"sv;
  294. TRY(Core::System::pledge("stdio thread recvfd sendfd rpath unix prot_exec map_fixed"));
  295. TRY(Core::System::unveil("/tmp/session/%sid/portal/filesystemaccess", "rw"));
  296. TRY(Core::System::unveil("/res", "r"));
  297. TRY(Core::System::unveil("/usr/lib", "r"));
  298. TRY(Core::System::unveil(nullptr, nullptr));
  299. // Construct the main window
  300. auto window = GUI::Window::construct();
  301. auto app_icon = GUI::Icon::default_icon("app-3d-file-viewer"sv);
  302. window->set_icon(app_icon.bitmap_for_size(16));
  303. window->set_title("3D File Viewer");
  304. window->resize(640 + 4, 480 + 4);
  305. window->set_resizable(false);
  306. window->set_double_buffering_enabled(true);
  307. auto widget = TRY(window->set_main_widget<GLContextWidget>());
  308. auto& time = widget->add<GUI::Label>();
  309. time.set_visible(false);
  310. time.set_foreground_role(ColorRole::HoverHighlight);
  311. time.set_relative_rect({ 0, 8, 100, 10 });
  312. time.set_text_alignment(Gfx::TextAlignment::CenterRight);
  313. time.set_x(widget->width() - time.width() - 6);
  314. widget->set_stat_label(time);
  315. auto file_menu = window->add_menu("&File"_string);
  316. file_menu->add_action(GUI::CommonActions::make_open_action([&](auto&) {
  317. FileSystemAccessClient::OpenFileOptions options {
  318. .allowed_file_types = { { GUI::FileTypeFilter { "Object Files", { { "obj" } } }, GUI::FileTypeFilter::all_files() } },
  319. };
  320. auto response = FileSystemAccessClient::Client::the().open_file(window, options);
  321. if (response.is_error())
  322. return;
  323. auto file = response.release_value();
  324. widget->load_file(file.filename(), file.release_stream());
  325. }));
  326. file_menu->add_separator();
  327. file_menu->add_action(GUI::CommonActions::make_quit_action([&](auto&) {
  328. app->quit();
  329. }));
  330. auto view_menu = window->add_menu("&View"_string);
  331. view_menu->add_action(GUI::CommonActions::make_fullscreen_action([&](auto&) {
  332. window->set_fullscreen(!window->is_fullscreen());
  333. }));
  334. auto rotation_axis_menu = view_menu->add_submenu("Rotation &Axis"_string);
  335. auto rotation_x_action = GUI::Action::create_checkable("&X", [&widget](auto&) {
  336. widget->toggle_rotate_x();
  337. });
  338. auto rotation_y_action = GUI::Action::create_checkable("&Y", [&widget](auto&) {
  339. widget->toggle_rotate_y();
  340. });
  341. auto rotation_z_action = GUI::Action::create_checkable("&Z", [&widget](auto&) {
  342. widget->toggle_rotate_z();
  343. });
  344. rotation_axis_menu->add_action(*rotation_x_action);
  345. rotation_axis_menu->add_action(*rotation_y_action);
  346. rotation_axis_menu->add_action(*rotation_z_action);
  347. rotation_x_action->set_checked(true);
  348. rotation_z_action->set_checked(true);
  349. auto rotation_speed_menu = view_menu->add_submenu("Rotation &Speed"_string);
  350. GUI::ActionGroup rotation_speed_actions;
  351. rotation_speed_actions.set_exclusive(true);
  352. auto no_rotation_action = GUI::Action::create_checkable("N&o Rotation", [&widget](auto&) {
  353. widget->set_rotation_speed(0.f);
  354. });
  355. auto slow_rotation_action = GUI::Action::create_checkable("&Slow", [&widget](auto&) {
  356. widget->set_rotation_speed(30.f);
  357. });
  358. auto normal_rotation_action = GUI::Action::create_checkable("&Normal", [&widget](auto&) {
  359. widget->set_rotation_speed(60.f);
  360. });
  361. auto fast_rotation_action = GUI::Action::create_checkable("&Fast", [&widget](auto&) {
  362. widget->set_rotation_speed(90.f);
  363. });
  364. rotation_speed_actions.add_action(*no_rotation_action);
  365. rotation_speed_actions.add_action(*slow_rotation_action);
  366. rotation_speed_actions.add_action(*normal_rotation_action);
  367. rotation_speed_actions.add_action(*fast_rotation_action);
  368. rotation_speed_menu->add_action(*no_rotation_action);
  369. rotation_speed_menu->add_action(*slow_rotation_action);
  370. rotation_speed_menu->add_action(*normal_rotation_action);
  371. rotation_speed_menu->add_action(*fast_rotation_action);
  372. normal_rotation_action->set_checked(true);
  373. auto show_frame_rate_action = GUI::Action::create_checkable("Show Frame &Rate", [&widget](auto&) {
  374. widget->toggle_show_frame_rate();
  375. });
  376. view_menu->add_action(*show_frame_rate_action);
  377. auto texture_menu = window->add_menu("&Texture"_string);
  378. auto texture_enabled_action = GUI::Action::create_checkable("&Enable Texture", [&widget](auto& action) {
  379. widget->set_texture_enabled(action.is_checked());
  380. });
  381. texture_enabled_action->set_checked(true);
  382. texture_menu->add_action(texture_enabled_action);
  383. auto wrap_u_menu = texture_menu->add_submenu("Wrap &S"_string);
  384. GUI::ActionGroup wrap_s_actions;
  385. wrap_s_actions.set_exclusive(true);
  386. auto wrap_u_repeat_action = GUI::Action::create_checkable("&Repeat", [&widget](auto&) {
  387. widget->set_wrap_s_mode(GL_REPEAT);
  388. });
  389. auto wrap_u_mirrored_repeat_action = GUI::Action::create_checkable("&Mirrored Repeat", [&widget](auto&) {
  390. widget->set_wrap_s_mode(GL_MIRRORED_REPEAT);
  391. });
  392. auto wrap_u_clamp_action = GUI::Action::create_checkable("&Clamp", [&widget](auto&) {
  393. widget->set_wrap_s_mode(GL_CLAMP);
  394. });
  395. wrap_s_actions.add_action(*wrap_u_repeat_action);
  396. wrap_s_actions.add_action(*wrap_u_mirrored_repeat_action);
  397. wrap_s_actions.add_action(*wrap_u_clamp_action);
  398. wrap_u_menu->add_action(*wrap_u_repeat_action);
  399. wrap_u_menu->add_action(*wrap_u_mirrored_repeat_action);
  400. wrap_u_menu->add_action(*wrap_u_clamp_action);
  401. wrap_u_repeat_action->set_checked(true);
  402. auto wrap_t_menu = texture_menu->add_submenu("Wrap &T"_string);
  403. GUI::ActionGroup wrap_t_actions;
  404. wrap_t_actions.set_exclusive(true);
  405. auto wrap_t_repeat_action = GUI::Action::create_checkable("&Repeat", [&widget](auto&) {
  406. widget->set_wrap_t_mode(GL_REPEAT);
  407. });
  408. auto wrap_t_mirrored_repeat_action = GUI::Action::create_checkable("&Mirrored Repeat", [&widget](auto&) {
  409. widget->set_wrap_t_mode(GL_MIRRORED_REPEAT);
  410. });
  411. auto wrap_t_clamp_action = GUI::Action::create_checkable("&Clamp", [&widget](auto&) {
  412. widget->set_wrap_t_mode(GL_CLAMP);
  413. });
  414. wrap_t_actions.add_action(*wrap_t_repeat_action);
  415. wrap_t_actions.add_action(*wrap_t_mirrored_repeat_action);
  416. wrap_t_actions.add_action(*wrap_t_clamp_action);
  417. wrap_t_menu->add_action(*wrap_t_repeat_action);
  418. wrap_t_menu->add_action(*wrap_t_mirrored_repeat_action);
  419. wrap_t_menu->add_action(*wrap_t_clamp_action);
  420. wrap_t_repeat_action->set_checked(true);
  421. auto texture_scale_menu = texture_menu->add_submenu("S&cale"_string);
  422. GUI::ActionGroup texture_scale_actions;
  423. texture_scale_actions.set_exclusive(true);
  424. auto texture_scale_025_action = GUI::Action::create_checkable("0.25x", [&widget](auto&) {
  425. widget->set_texture_scale(0.25f);
  426. });
  427. auto texture_scale_05_action = GUI::Action::create_checkable("0.5x", [&widget](auto&) {
  428. widget->set_texture_scale(0.5f);
  429. });
  430. auto texture_scale_1_action = GUI::Action::create_checkable("1x", [&widget](auto&) {
  431. widget->set_texture_scale(1);
  432. });
  433. auto texture_scale_2_action = GUI::Action::create_checkable("2x", [&widget](auto&) {
  434. widget->set_texture_scale(2);
  435. });
  436. auto texture_scale_4_action = GUI::Action::create_checkable("4x", [&widget](auto&) {
  437. widget->set_texture_scale(4);
  438. });
  439. texture_scale_actions.add_action(*texture_scale_025_action);
  440. texture_scale_actions.add_action(*texture_scale_05_action);
  441. texture_scale_actions.add_action(*texture_scale_1_action);
  442. texture_scale_actions.add_action(*texture_scale_2_action);
  443. texture_scale_actions.add_action(*texture_scale_4_action);
  444. texture_scale_menu->add_action(*texture_scale_025_action);
  445. texture_scale_menu->add_action(*texture_scale_05_action);
  446. texture_scale_menu->add_action(*texture_scale_1_action);
  447. texture_scale_menu->add_action(*texture_scale_2_action);
  448. texture_scale_menu->add_action(*texture_scale_4_action);
  449. texture_scale_1_action->set_checked(true);
  450. auto texture_mag_filter_menu = texture_menu->add_submenu("Mag Filter"_string);
  451. GUI::ActionGroup texture_mag_filter_actions;
  452. texture_mag_filter_actions.set_exclusive(true);
  453. auto texture_mag_filter_nearest_action = GUI::Action::create_checkable("&Nearest", [&widget](auto&) {
  454. widget->set_mag_filter(GL_NEAREST);
  455. });
  456. auto texture_mag_filter_linear_action = GUI::Action::create_checkable("&Linear", [&widget](auto&) {
  457. widget->set_mag_filter(GL_LINEAR);
  458. });
  459. texture_mag_filter_actions.add_action(*texture_mag_filter_nearest_action);
  460. texture_mag_filter_actions.add_action(*texture_mag_filter_linear_action);
  461. texture_mag_filter_menu->add_action(*texture_mag_filter_nearest_action);
  462. texture_mag_filter_menu->add_action(*texture_mag_filter_linear_action);
  463. texture_mag_filter_nearest_action->set_checked(true);
  464. auto help_menu = window->add_menu("&Help"_string);
  465. help_menu->add_action(GUI::CommonActions::make_command_palette_action(window));
  466. help_menu->add_action(GUI::CommonActions::make_about_action("3D File Viewer"_string, app_icon, window));
  467. window->show();
  468. auto file = FileSystemAccessClient::Client::the().request_file_read_only_approved(window, filename);
  469. if (file.is_error()) {
  470. if (file.error().code() != ENOENT)
  471. GUI::MessageBox::show(window, DeprecatedString::formatted("Opening \"{}\" failed: {}", filename, strerror(errno)), "Error"sv, GUI::MessageBox::Type::Error);
  472. return 1;
  473. }
  474. widget->load_file(file.value().filename(), file.value().release_stream());
  475. return app->exec();
  476. }