2021-05-02 11:19:50 -03:00
|
|
|
/*
|
|
|
|
|
* Copyright (c) 2021, Jesse Buhagiar <jooster669@gmail.com>
|
|
|
|
|
*
|
|
|
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
|
|
|
*/
|
|
|
|
|
|
2021-08-01 12:24:57 -03:00
|
|
|
#include <LibCore/ElapsedTimer.h>
|
2021-05-17 17:20:01 -03:00
|
|
|
#include <LibCore/File.h>
|
2021-08-18 10:33:09 -03:00
|
|
|
#include <LibFileSystemAccessClient/Client.h>
|
2021-05-02 11:19:50 -03:00
|
|
|
#include <LibGL/GL/gl.h>
|
|
|
|
|
#include <LibGL/GLContext.h>
|
2021-05-25 19:01:32 -03:00
|
|
|
#include <LibGUI/ActionGroup.h>
|
2021-05-02 11:19:50 -03:00
|
|
|
#include <LibGUI/Application.h>
|
2021-05-17 17:20:01 -03:00
|
|
|
#include <LibGUI/FilePicker.h>
|
2021-05-02 11:19:50 -03:00
|
|
|
#include <LibGUI/Icon.h>
|
2021-08-01 12:24:57 -03:00
|
|
|
#include <LibGUI/Label.h>
|
2021-05-17 17:20:01 -03:00
|
|
|
#include <LibGUI/Menu.h>
|
|
|
|
|
#include <LibGUI/Menubar.h>
|
|
|
|
|
#include <LibGUI/MessageBox.h>
|
2021-05-02 11:19:50 -03:00
|
|
|
#include <LibGUI/Painter.h>
|
|
|
|
|
#include <LibGUI/Widget.h>
|
|
|
|
|
#include <LibGUI/Window.h>
|
|
|
|
|
#include <LibGfx/Bitmap.h>
|
2021-08-01 12:24:57 -03:00
|
|
|
#include <LibGfx/Palette.h>
|
2021-05-02 11:19:50 -03:00
|
|
|
#include <unistd.h>
|
|
|
|
|
|
|
|
|
|
#include "Mesh.h"
|
|
|
|
|
#include "WavefrontOBJLoader.h"
|
|
|
|
|
|
|
|
|
|
static constexpr u16 RENDER_WIDTH = 640;
|
|
|
|
|
static constexpr u16 RENDER_HEIGHT = 480;
|
|
|
|
|
|
2021-05-21 11:14:40 -03:00
|
|
|
class GLContextWidget final : public GUI::Frame {
|
|
|
|
|
C_OBJECT(GLContextWidget);
|
|
|
|
|
|
2021-05-02 11:19:50 -03:00
|
|
|
public:
|
2021-08-18 10:33:09 -03:00
|
|
|
bool load_path(String const& fname);
|
|
|
|
|
bool load_fd_and_close(int fd, String const& fname);
|
|
|
|
|
bool load_file(Core::File& file, String const& fname);
|
2021-05-25 19:01:32 -03:00
|
|
|
void toggle_rotate_x() { m_rotate_x = !m_rotate_x; }
|
|
|
|
|
void toggle_rotate_y() { m_rotate_y = !m_rotate_y; }
|
|
|
|
|
void toggle_rotate_z() { m_rotate_z = !m_rotate_z; }
|
|
|
|
|
void set_rotation_speed(float speed) { m_rotation_speed = speed; }
|
2021-08-01 12:24:57 -03:00
|
|
|
void set_stat_label(RefPtr<GUI::Label> l) { m_stats = l; };
|
2021-08-11 18:43:28 -03:00
|
|
|
void set_wrap_s_mode(GLint mode) { m_wrap_s_mode = mode; }
|
|
|
|
|
void set_wrap_t_mode(GLint mode) { m_wrap_t_mode = mode; }
|
|
|
|
|
void set_texture_scale(float scale) { m_texture_scale = scale; }
|
2021-08-12 15:39:32 -03:00
|
|
|
void set_mag_filter(GLint filter) { m_mag_filter = filter; }
|
2021-08-01 12:24:57 -03:00
|
|
|
|
|
|
|
|
void toggle_show_frame_rate()
|
|
|
|
|
{
|
|
|
|
|
m_show_frame_rate = !m_show_frame_rate;
|
|
|
|
|
m_stats->set_visible(m_show_frame_rate);
|
|
|
|
|
}
|
2021-05-17 17:20:01 -03:00
|
|
|
|
2021-05-02 11:19:50 -03:00
|
|
|
private:
|
|
|
|
|
GLContextWidget()
|
2021-05-17 17:20:01 -03:00
|
|
|
: m_mesh_loader(adopt_own(*new WavefrontOBJLoader()))
|
2021-05-02 11:19:50 -03:00
|
|
|
{
|
2021-07-21 13:02:15 -03:00
|
|
|
m_bitmap = Gfx::Bitmap::try_create(Gfx::BitmapFormat::BGRx8888, { RENDER_WIDTH, RENDER_HEIGHT });
|
2021-05-02 11:19:50 -03:00
|
|
|
m_context = GL::create_context(*m_bitmap);
|
|
|
|
|
|
|
|
|
|
start_timer(20);
|
|
|
|
|
|
|
|
|
|
GL::make_context_current(m_context);
|
2021-08-16 14:25:16 -03:00
|
|
|
glFrontFace(GL_CCW);
|
2021-05-02 11:19:50 -03:00
|
|
|
glEnable(GL_CULL_FACE);
|
2021-05-08 18:20:11 -03:00
|
|
|
glEnable(GL_DEPTH_TEST);
|
2021-05-02 11:19:50 -03:00
|
|
|
|
|
|
|
|
// Set projection matrix
|
|
|
|
|
glMatrixMode(GL_PROJECTION);
|
|
|
|
|
glLoadIdentity();
|
|
|
|
|
glFrustum(-0.5, 0.5, -0.5, 0.5, 1, 1500);
|
|
|
|
|
|
2021-05-09 02:25:38 -03:00
|
|
|
m_init_list = glGenLists(1);
|
|
|
|
|
glNewList(m_init_list, GL_COMPILE);
|
|
|
|
|
{
|
|
|
|
|
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
|
|
|
|
|
glClearDepth(1.0);
|
|
|
|
|
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
|
|
|
|
}
|
|
|
|
|
glEndList();
|
2021-05-02 11:19:50 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
virtual void paint_event(GUI::PaintEvent&) override;
|
2021-09-01 14:11:33 -03:00
|
|
|
virtual void resize_event(GUI::ResizeEvent&) override;
|
2021-05-02 11:19:50 -03:00
|
|
|
virtual void timer_event(Core::TimerEvent&) override;
|
2021-06-02 10:22:08 -03:00
|
|
|
virtual void mousemove_event(GUI::MouseEvent&) override;
|
2021-08-12 15:39:58 -03:00
|
|
|
virtual void mousewheel_event(GUI::MouseEvent&) override;
|
2021-05-02 11:19:50 -03:00
|
|
|
|
|
|
|
|
private:
|
2021-05-17 17:20:01 -03:00
|
|
|
RefPtr<Mesh> m_mesh;
|
2021-05-02 11:19:50 -03:00
|
|
|
RefPtr<Gfx::Bitmap> m_bitmap;
|
|
|
|
|
OwnPtr<GL::GLContext> m_context;
|
2021-05-17 17:20:01 -03:00
|
|
|
NonnullOwnPtr<WavefrontOBJLoader> m_mesh_loader;
|
2021-05-09 02:25:38 -03:00
|
|
|
GLuint m_init_list { 0 };
|
2021-05-25 19:01:32 -03:00
|
|
|
bool m_rotate_x = true;
|
|
|
|
|
bool m_rotate_y = false;
|
|
|
|
|
bool m_rotate_z = true;
|
2021-06-02 10:22:08 -03:00
|
|
|
float m_angle_x = 0.0;
|
|
|
|
|
float m_angle_y = 0.0;
|
|
|
|
|
float m_angle_z = 0.0;
|
|
|
|
|
Gfx::IntPoint m_last_mouse;
|
2021-08-16 09:01:48 -03:00
|
|
|
float m_rotation_speed = 60.f;
|
2021-08-01 12:24:57 -03:00
|
|
|
bool m_show_frame_rate = false;
|
|
|
|
|
int m_cycles = 0;
|
|
|
|
|
int m_accumulated_time = 0;
|
|
|
|
|
RefPtr<GUI::Label> m_stats;
|
2021-08-11 18:43:28 -03:00
|
|
|
GLint m_wrap_s_mode = GL_REPEAT;
|
|
|
|
|
GLint m_wrap_t_mode = GL_REPEAT;
|
|
|
|
|
float m_texture_scale = 1.0f;
|
2021-08-12 15:39:32 -03:00
|
|
|
GLint m_mag_filter = GL_NEAREST;
|
2021-08-12 15:39:58 -03:00
|
|
|
float m_zoom = 1;
|
2021-05-02 11:19:50 -03:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
void GLContextWidget::paint_event(GUI::PaintEvent& event)
|
|
|
|
|
{
|
2021-05-21 11:14:40 -03:00
|
|
|
GUI::Frame::paint_event(event);
|
|
|
|
|
|
2021-05-02 11:19:50 -03:00
|
|
|
GUI::Painter painter(*this);
|
|
|
|
|
painter.add_clip_rect(event.rect());
|
2021-05-21 11:14:40 -03:00
|
|
|
painter.draw_scaled_bitmap(frame_inner_rect(), *m_bitmap, m_bitmap->rect());
|
2021-05-02 11:19:50 -03:00
|
|
|
}
|
|
|
|
|
|
2021-09-01 14:11:33 -03:00
|
|
|
void GLContextWidget::resize_event(GUI::ResizeEvent& event)
|
|
|
|
|
{
|
|
|
|
|
GUI::Frame::resize_event(event);
|
|
|
|
|
|
|
|
|
|
if (m_stats)
|
|
|
|
|
m_stats->set_x(width() - m_stats->width());
|
|
|
|
|
};
|
|
|
|
|
|
2021-06-02 10:22:08 -03:00
|
|
|
void GLContextWidget::mousemove_event(GUI::MouseEvent& event)
|
|
|
|
|
{
|
|
|
|
|
if (event.buttons() == GUI::MouseButton::Left) {
|
|
|
|
|
int delta_x = m_last_mouse.x() - event.x();
|
|
|
|
|
int delta_y = m_last_mouse.y() - event.y();
|
|
|
|
|
|
2021-08-16 09:01:48 -03:00
|
|
|
m_angle_x -= delta_y / 2.0f;
|
|
|
|
|
m_angle_y -= delta_x / 2.0f;
|
2021-06-02 10:22:08 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
m_last_mouse = event.position();
|
|
|
|
|
}
|
|
|
|
|
|
2021-08-12 15:39:58 -03:00
|
|
|
void GLContextWidget::mousewheel_event(GUI::MouseEvent& event)
|
|
|
|
|
{
|
|
|
|
|
if (event.wheel_delta() > 0)
|
|
|
|
|
m_zoom /= 1.1f;
|
|
|
|
|
else
|
|
|
|
|
m_zoom *= 1.1f;
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-02 11:19:50 -03:00
|
|
|
void GLContextWidget::timer_event(Core::TimerEvent&)
|
|
|
|
|
{
|
2021-09-12 12:53:55 -03:00
|
|
|
auto timer = Core::ElapsedTimer::start_new();
|
2021-08-01 12:24:57 -03:00
|
|
|
|
2021-05-09 02:25:38 -03:00
|
|
|
glCallList(m_init_list);
|
2021-05-02 11:19:50 -03:00
|
|
|
|
2021-06-02 10:22:08 -03:00
|
|
|
if (m_rotate_x)
|
|
|
|
|
m_angle_x -= m_rotation_speed * 0.01f;
|
|
|
|
|
if (m_rotate_y)
|
|
|
|
|
m_angle_y -= m_rotation_speed * 0.01f;
|
|
|
|
|
if (m_rotate_z)
|
|
|
|
|
m_angle_z -= m_rotation_speed * 0.01f;
|
2021-05-02 11:19:50 -03:00
|
|
|
glMatrixMode(GL_MODELVIEW);
|
2021-05-25 19:01:32 -03:00
|
|
|
glLoadIdentity();
|
|
|
|
|
glTranslatef(0, 0, -8.5);
|
2021-06-02 10:22:08 -03:00
|
|
|
glRotatef(m_angle_x, 1, 0, 0);
|
|
|
|
|
glRotatef(m_angle_y, 0, 1, 0);
|
|
|
|
|
glRotatef(m_angle_z, 0, 0, 1);
|
2021-05-02 11:19:50 -03:00
|
|
|
|
2021-08-11 18:43:28 -03:00
|
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, m_wrap_s_mode);
|
|
|
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, m_wrap_t_mode);
|
2021-08-12 15:39:32 -03:00
|
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, m_mag_filter);
|
2021-08-12 15:39:58 -03:00
|
|
|
glScalef(m_zoom, m_zoom, m_zoom);
|
2021-08-11 18:43:28 -03:00
|
|
|
|
2021-05-17 18:45:15 -03:00
|
|
|
if (!m_mesh.is_null())
|
2021-08-11 18:43:28 -03:00
|
|
|
m_mesh->draw(m_texture_scale);
|
2021-05-02 11:19:50 -03:00
|
|
|
|
|
|
|
|
m_context->present();
|
2021-08-01 12:24:57 -03:00
|
|
|
|
|
|
|
|
if ((m_cycles % 30) == 0) {
|
|
|
|
|
int render_time = m_accumulated_time / 30;
|
|
|
|
|
int frame_rate = render_time > 0 ? 1000 / render_time : 0;
|
|
|
|
|
m_stats->set_text(String::formatted("{} fps, {} ms", frame_rate, render_time));
|
|
|
|
|
m_accumulated_time = 0;
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-02 11:19:50 -03:00
|
|
|
update();
|
2021-08-01 12:24:57 -03:00
|
|
|
|
|
|
|
|
m_accumulated_time += timer.elapsed();
|
|
|
|
|
m_cycles++;
|
2021-05-02 11:19:50 -03:00
|
|
|
}
|
|
|
|
|
|
2021-08-18 10:33:09 -03:00
|
|
|
bool GLContextWidget::load_path(String const& filename)
|
2021-05-17 17:20:01 -03:00
|
|
|
{
|
2021-05-20 16:50:53 -03:00
|
|
|
auto file = Core::File::construct(filename);
|
|
|
|
|
|
2021-08-18 10:33:09 -03:00
|
|
|
if (!file->open(Core::OpenMode::ReadOnly) && file->error() != ENOENT) {
|
|
|
|
|
GUI::MessageBox::show(window(), String::formatted("Opening \"{}\" failed: {}", filename, strerror(errno)), "Error", GUI::MessageBox::Type::Error);
|
2021-05-20 16:50:53 -03:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2021-08-18 10:33:09 -03:00
|
|
|
return load_file(file, filename);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool GLContextWidget::load_fd_and_close(int fd, String const& filename)
|
|
|
|
|
{
|
|
|
|
|
auto file = Core::File::construct();
|
|
|
|
|
|
|
|
|
|
if (!file->open(fd, Core::OpenMode::ReadOnly, Core::File::ShouldCloseFileDescriptor::Yes) && file->error() != ENOENT) {
|
2021-05-20 16:50:53 -03:00
|
|
|
GUI::MessageBox::show(window(), String::formatted("Opening \"{}\" failed: {}", filename, strerror(errno)), "Error", GUI::MessageBox::Type::Error);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2021-08-18 10:33:09 -03:00
|
|
|
return load_file(file, filename);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool GLContextWidget::load_file(Core::File& file, String const& filename)
|
|
|
|
|
{
|
|
|
|
|
if (!filename.ends_with(".obj")) {
|
|
|
|
|
GUI::MessageBox::show(window(), String::formatted("Opening \"{}\" failed: invalid file type", filename), "Error", GUI::MessageBox::Type::Error);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (file.is_device()) {
|
2021-05-20 16:50:53 -03:00
|
|
|
GUI::MessageBox::show(window(), String::formatted("Opening \"{}\" failed: Can't open device files", filename), "Error", GUI::MessageBox::Type::Error);
|
|
|
|
|
return false;
|
2021-05-17 18:45:15 -03:00
|
|
|
}
|
|
|
|
|
|
2021-08-18 10:33:09 -03:00
|
|
|
if (file.is_directory()) {
|
2021-05-20 16:50:53 -03:00
|
|
|
GUI::MessageBox::show(window(), String::formatted("Opening \"{}\" failed: Can't open directories", filename), "Error", GUI::MessageBox::Type::Error);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
auto new_mesh = m_mesh_loader->load(file);
|
|
|
|
|
if (new_mesh.is_null()) {
|
|
|
|
|
GUI::MessageBox::show(window(), String::formatted("Reading \"{}\" failed.", filename), "Error", GUI::MessageBox::Type::Error);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-23 10:57:53 -03:00
|
|
|
// Determine whether or not a texture for this model resides within the same directory
|
|
|
|
|
StringBuilder builder;
|
|
|
|
|
builder.append(filename.split('.').at(0));
|
|
|
|
|
builder.append(".bmp");
|
|
|
|
|
|
2021-08-18 10:33:09 -03:00
|
|
|
String texture_path = Core::File::absolute_path(builder.string_view());
|
|
|
|
|
|
2021-05-23 10:57:53 -03:00
|
|
|
// Attempt to open the texture file from disk
|
2021-08-18 10:33:09 -03:00
|
|
|
RefPtr<Gfx::Bitmap> texture_image;
|
|
|
|
|
if (Core::File::exists(texture_path)) {
|
|
|
|
|
texture_image = Gfx::Bitmap::try_load_from_file(texture_path);
|
|
|
|
|
} else {
|
|
|
|
|
auto result = FileSystemAccessClient::Client::the().request_file(window()->window_id(), builder.string_view(), Core::OpenMode::ReadOnly);
|
|
|
|
|
|
|
|
|
|
if (result.error != 0) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
texture_image = Gfx::Bitmap::try_load_from_fd_and_close(*result.fd, *result.chosen_file);
|
|
|
|
|
}
|
2021-05-23 10:57:53 -03:00
|
|
|
|
|
|
|
|
GLuint tex;
|
|
|
|
|
glGenTextures(1, &tex);
|
|
|
|
|
if (texture_image) {
|
|
|
|
|
// Upload texture data to the GL
|
2021-05-29 19:15:51 -03:00
|
|
|
glBindTexture(GL_TEXTURE_2D, tex);
|
2021-08-18 08:20:00 -03:00
|
|
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, texture_image->width(), texture_image->height(), 0, GL_BGRA, GL_UNSIGNED_BYTE, texture_image->scanline(0));
|
2021-05-23 10:57:53 -03:00
|
|
|
} else {
|
|
|
|
|
dbgln("3DFileViewer: Couldn't load texture for {}", filename);
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-20 16:50:53 -03:00
|
|
|
m_mesh = new_mesh;
|
|
|
|
|
dbgln("3DFileViewer: mesh has {} triangles.", m_mesh->triangle_count());
|
2021-05-23 10:57:53 -03:00
|
|
|
|
2021-05-20 16:50:53 -03:00
|
|
|
return true;
|
2021-05-17 17:20:01 -03:00
|
|
|
}
|
|
|
|
|
|
2021-05-02 11:19:50 -03:00
|
|
|
int main(int argc, char** argv)
|
|
|
|
|
{
|
|
|
|
|
auto app = GUI::Application::construct(argc, argv);
|
|
|
|
|
|
2021-08-18 10:33:09 -03:00
|
|
|
if (pledge("stdio thread recvfd sendfd rpath unix", nullptr) < 0) {
|
2021-05-02 11:19:50 -03:00
|
|
|
perror("pledge");
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
2021-08-18 10:33:09 -03:00
|
|
|
if (unveil("/tmp/portal/filesystemaccess", "rw") < 0) {
|
|
|
|
|
perror("unveil");
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (unveil("/home/anon/Documents/3D Models/teapot.obj", "r") < 0) {
|
|
|
|
|
perror("unveil");
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (unveil("/home/anon/Documents/3D Models/teapot.bmp", "r") < 0) {
|
|
|
|
|
perror("unveil");
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (unveil("/res", "r") < 0) {
|
|
|
|
|
perror("unveil");
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (unveil(nullptr, nullptr) < 0) {
|
|
|
|
|
perror("unveil");
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-02 11:19:50 -03:00
|
|
|
// Construct the main window
|
|
|
|
|
auto window = GUI::Window::construct();
|
2021-05-19 14:57:59 -03:00
|
|
|
auto app_icon = GUI::Icon::default_icon("app-3d-file-viewer");
|
2021-05-02 11:19:50 -03:00
|
|
|
window->set_icon(app_icon.bitmap_for_size(16));
|
2021-05-19 14:57:59 -03:00
|
|
|
window->set_title("3D File Viewer");
|
2021-05-21 11:14:40 -03:00
|
|
|
window->resize(640 + 4, 480 + 4);
|
2021-05-02 11:19:50 -03:00
|
|
|
window->set_resizable(false);
|
|
|
|
|
window->set_double_buffering_enabled(true);
|
2021-05-17 17:20:01 -03:00
|
|
|
auto& widget = window->set_main_widget<GLContextWidget>();
|
|
|
|
|
|
2021-08-01 12:24:57 -03:00
|
|
|
auto& time = widget.add<GUI::Label>();
|
|
|
|
|
time.set_visible(false);
|
|
|
|
|
time.set_foreground_role(ColorRole::HoverHighlight);
|
|
|
|
|
time.set_relative_rect({ 0, 8, 86, 10 });
|
2021-09-01 14:11:33 -03:00
|
|
|
time.set_x(widget.width() - time.width());
|
2021-08-01 12:24:57 -03:00
|
|
|
widget.set_stat_label(time);
|
|
|
|
|
|
2021-07-21 16:21:03 -03:00
|
|
|
auto& file_menu = window->add_menu("&File");
|
2021-05-17 17:20:01 -03:00
|
|
|
|
|
|
|
|
file_menu.add_action(GUI::CommonActions::make_open_action([&](auto&) {
|
2021-08-18 10:33:09 -03:00
|
|
|
auto result = FileSystemAccessClient::Client::the().open_file(window->window_id());
|
2021-05-17 17:20:01 -03:00
|
|
|
|
2021-08-18 10:33:09 -03:00
|
|
|
if (result.error != 0)
|
2021-05-17 17:20:01 -03:00
|
|
|
return;
|
|
|
|
|
|
2021-08-18 10:33:09 -03:00
|
|
|
if (widget.load_fd_and_close(*result.fd, *result.chosen_file)) {
|
|
|
|
|
auto canonical_path = Core::File::absolute_path(*result.chosen_file);
|
|
|
|
|
window->set_title(String::formatted("{} - 3D File Viewer", canonical_path));
|
|
|
|
|
}
|
2021-05-17 17:20:01 -03:00
|
|
|
}));
|
2021-05-27 14:11:03 -03:00
|
|
|
file_menu.add_separator();
|
AK+Everywhere: Disallow constructing Functions from incompatible types
Previously, AK::Function would accept _any_ callable type, and try to
call it when called, first with the given set of arguments, then with
zero arguments, and if all of those failed, it would simply not call the
function and **return a value-constructed Out type**.
This lead to many, many, many hard to debug situations when someone
forgot a `const` in their lambda argument types, and many cases of
people taking zero arguments in their lambdas to ignore them.
This commit reworks the Function interface to not include any such
surprising behaviour, if your function instance is not callable with
the declared argument set of the Function, it can simply not be
assigned to that Function instance, end of story.
2021-06-05 15:34:31 -03:00
|
|
|
file_menu.add_action(GUI::CommonActions::make_quit_action([&](auto&) {
|
2021-05-17 17:20:01 -03:00
|
|
|
app->quit();
|
|
|
|
|
}));
|
2021-05-02 11:19:50 -03:00
|
|
|
|
2021-07-21 16:21:03 -03:00
|
|
|
auto& view_menu = window->add_menu("&View");
|
AK+Everywhere: Disallow constructing Functions from incompatible types
Previously, AK::Function would accept _any_ callable type, and try to
call it when called, first with the given set of arguments, then with
zero arguments, and if all of those failed, it would simply not call the
function and **return a value-constructed Out type**.
This lead to many, many, many hard to debug situations when someone
forgot a `const` in their lambda argument types, and many cases of
people taking zero arguments in their lambdas to ignore them.
This commit reworks the Function interface to not include any such
surprising behaviour, if your function instance is not callable with
the declared argument set of the Function, it can simply not be
assigned to that Function instance, end of story.
2021-06-05 15:34:31 -03:00
|
|
|
view_menu.add_action(GUI::CommonActions::make_fullscreen_action([&](auto&) {
|
2021-05-25 19:01:32 -03:00
|
|
|
window->set_fullscreen(!window->is_fullscreen());
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
auto& rotation_axis_menu = view_menu.add_submenu("Rotation &Axis");
|
AK+Everywhere: Disallow constructing Functions from incompatible types
Previously, AK::Function would accept _any_ callable type, and try to
call it when called, first with the given set of arguments, then with
zero arguments, and if all of those failed, it would simply not call the
function and **return a value-constructed Out type**.
This lead to many, many, many hard to debug situations when someone
forgot a `const` in their lambda argument types, and many cases of
people taking zero arguments in their lambdas to ignore them.
This commit reworks the Function interface to not include any such
surprising behaviour, if your function instance is not callable with
the declared argument set of the Function, it can simply not be
assigned to that Function instance, end of story.
2021-06-05 15:34:31 -03:00
|
|
|
auto rotation_x_action = GUI::Action::create_checkable("&X", [&widget](auto&) {
|
2021-05-25 19:01:32 -03:00
|
|
|
widget.toggle_rotate_x();
|
|
|
|
|
});
|
AK+Everywhere: Disallow constructing Functions from incompatible types
Previously, AK::Function would accept _any_ callable type, and try to
call it when called, first with the given set of arguments, then with
zero arguments, and if all of those failed, it would simply not call the
function and **return a value-constructed Out type**.
This lead to many, many, many hard to debug situations when someone
forgot a `const` in their lambda argument types, and many cases of
people taking zero arguments in their lambdas to ignore them.
This commit reworks the Function interface to not include any such
surprising behaviour, if your function instance is not callable with
the declared argument set of the Function, it can simply not be
assigned to that Function instance, end of story.
2021-06-05 15:34:31 -03:00
|
|
|
auto rotation_y_action = GUI::Action::create_checkable("&Y", [&widget](auto&) {
|
2021-05-25 19:01:32 -03:00
|
|
|
widget.toggle_rotate_y();
|
|
|
|
|
});
|
AK+Everywhere: Disallow constructing Functions from incompatible types
Previously, AK::Function would accept _any_ callable type, and try to
call it when called, first with the given set of arguments, then with
zero arguments, and if all of those failed, it would simply not call the
function and **return a value-constructed Out type**.
This lead to many, many, many hard to debug situations when someone
forgot a `const` in their lambda argument types, and many cases of
people taking zero arguments in their lambdas to ignore them.
This commit reworks the Function interface to not include any such
surprising behaviour, if your function instance is not callable with
the declared argument set of the Function, it can simply not be
assigned to that Function instance, end of story.
2021-06-05 15:34:31 -03:00
|
|
|
auto rotation_z_action = GUI::Action::create_checkable("&Z", [&widget](auto&) {
|
2021-05-25 19:01:32 -03:00
|
|
|
widget.toggle_rotate_z();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
rotation_axis_menu.add_action(*rotation_x_action);
|
|
|
|
|
rotation_axis_menu.add_action(*rotation_y_action);
|
|
|
|
|
rotation_axis_menu.add_action(*rotation_z_action);
|
|
|
|
|
|
|
|
|
|
rotation_x_action->set_checked(true);
|
|
|
|
|
rotation_z_action->set_checked(true);
|
|
|
|
|
|
|
|
|
|
auto& rotation_speed_menu = view_menu.add_submenu("Rotation &Speed");
|
|
|
|
|
GUI::ActionGroup rotation_speed_actions;
|
|
|
|
|
rotation_speed_actions.set_exclusive(true);
|
|
|
|
|
|
AK+Everywhere: Disallow constructing Functions from incompatible types
Previously, AK::Function would accept _any_ callable type, and try to
call it when called, first with the given set of arguments, then with
zero arguments, and if all of those failed, it would simply not call the
function and **return a value-constructed Out type**.
This lead to many, many, many hard to debug situations when someone
forgot a `const` in their lambda argument types, and many cases of
people taking zero arguments in their lambdas to ignore them.
This commit reworks the Function interface to not include any such
surprising behaviour, if your function instance is not callable with
the declared argument set of the Function, it can simply not be
assigned to that Function instance, end of story.
2021-06-05 15:34:31 -03:00
|
|
|
auto no_rotation_action = GUI::Action::create_checkable("N&o Rotation", [&widget](auto&) {
|
2021-05-25 19:01:32 -03:00
|
|
|
widget.set_rotation_speed(0.f);
|
|
|
|
|
});
|
AK+Everywhere: Disallow constructing Functions from incompatible types
Previously, AK::Function would accept _any_ callable type, and try to
call it when called, first with the given set of arguments, then with
zero arguments, and if all of those failed, it would simply not call the
function and **return a value-constructed Out type**.
This lead to many, many, many hard to debug situations when someone
forgot a `const` in their lambda argument types, and many cases of
people taking zero arguments in their lambdas to ignore them.
This commit reworks the Function interface to not include any such
surprising behaviour, if your function instance is not callable with
the declared argument set of the Function, it can simply not be
assigned to that Function instance, end of story.
2021-06-05 15:34:31 -03:00
|
|
|
auto slow_rotation_action = GUI::Action::create_checkable("&Slow", [&widget](auto&) {
|
2021-08-16 09:01:48 -03:00
|
|
|
widget.set_rotation_speed(30.f);
|
2021-05-25 19:01:32 -03:00
|
|
|
});
|
AK+Everywhere: Disallow constructing Functions from incompatible types
Previously, AK::Function would accept _any_ callable type, and try to
call it when called, first with the given set of arguments, then with
zero arguments, and if all of those failed, it would simply not call the
function and **return a value-constructed Out type**.
This lead to many, many, many hard to debug situations when someone
forgot a `const` in their lambda argument types, and many cases of
people taking zero arguments in their lambdas to ignore them.
This commit reworks the Function interface to not include any such
surprising behaviour, if your function instance is not callable with
the declared argument set of the Function, it can simply not be
assigned to that Function instance, end of story.
2021-06-05 15:34:31 -03:00
|
|
|
auto normal_rotation_action = GUI::Action::create_checkable("&Normal", [&widget](auto&) {
|
2021-08-16 09:01:48 -03:00
|
|
|
widget.set_rotation_speed(60.f);
|
2021-05-25 19:01:32 -03:00
|
|
|
});
|
AK+Everywhere: Disallow constructing Functions from incompatible types
Previously, AK::Function would accept _any_ callable type, and try to
call it when called, first with the given set of arguments, then with
zero arguments, and if all of those failed, it would simply not call the
function and **return a value-constructed Out type**.
This lead to many, many, many hard to debug situations when someone
forgot a `const` in their lambda argument types, and many cases of
people taking zero arguments in their lambdas to ignore them.
This commit reworks the Function interface to not include any such
surprising behaviour, if your function instance is not callable with
the declared argument set of the Function, it can simply not be
assigned to that Function instance, end of story.
2021-06-05 15:34:31 -03:00
|
|
|
auto fast_rotation_action = GUI::Action::create_checkable("&Fast", [&widget](auto&) {
|
2021-08-16 09:01:48 -03:00
|
|
|
widget.set_rotation_speed(90.f);
|
2021-05-25 19:01:32 -03:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
rotation_speed_actions.add_action(*no_rotation_action);
|
|
|
|
|
rotation_speed_actions.add_action(*slow_rotation_action);
|
|
|
|
|
rotation_speed_actions.add_action(*normal_rotation_action);
|
|
|
|
|
rotation_speed_actions.add_action(*fast_rotation_action);
|
|
|
|
|
|
|
|
|
|
rotation_speed_menu.add_action(*no_rotation_action);
|
|
|
|
|
rotation_speed_menu.add_action(*slow_rotation_action);
|
|
|
|
|
rotation_speed_menu.add_action(*normal_rotation_action);
|
|
|
|
|
rotation_speed_menu.add_action(*fast_rotation_action);
|
|
|
|
|
|
|
|
|
|
normal_rotation_action->set_checked(true);
|
|
|
|
|
|
2021-08-01 12:24:57 -03:00
|
|
|
auto show_frame_rate_action = GUI::Action::create_checkable("Show Frame &Rate", [&widget](auto&) {
|
|
|
|
|
widget.toggle_show_frame_rate();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
view_menu.add_action(*show_frame_rate_action);
|
|
|
|
|
|
2021-08-11 18:43:28 -03:00
|
|
|
auto& texture_menu = window->add_menu("&Texture");
|
|
|
|
|
|
|
|
|
|
auto& wrap_u_menu = texture_menu.add_submenu("Wrap &S");
|
|
|
|
|
GUI::ActionGroup wrap_s_actions;
|
|
|
|
|
wrap_s_actions.set_exclusive(true);
|
|
|
|
|
|
|
|
|
|
auto wrap_u_repeat_action = GUI::Action::create_checkable("&Repeat", [&widget](auto&) {
|
|
|
|
|
widget.set_wrap_s_mode(GL_REPEAT);
|
|
|
|
|
});
|
|
|
|
|
auto wrap_u_mirrored_repeat_action = GUI::Action::create_checkable("&Mirrored Repeat", [&widget](auto&) {
|
|
|
|
|
widget.set_wrap_s_mode(GL_MIRRORED_REPEAT);
|
|
|
|
|
});
|
|
|
|
|
auto wrap_u_clamp_action = GUI::Action::create_checkable("&Clamp", [&widget](auto&) {
|
|
|
|
|
widget.set_wrap_s_mode(GL_CLAMP);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
wrap_s_actions.add_action(*wrap_u_repeat_action);
|
|
|
|
|
wrap_s_actions.add_action(*wrap_u_mirrored_repeat_action);
|
|
|
|
|
wrap_s_actions.add_action(*wrap_u_clamp_action);
|
|
|
|
|
|
|
|
|
|
wrap_u_menu.add_action(*wrap_u_repeat_action);
|
|
|
|
|
wrap_u_menu.add_action(*wrap_u_mirrored_repeat_action);
|
|
|
|
|
wrap_u_menu.add_action(*wrap_u_clamp_action);
|
|
|
|
|
|
|
|
|
|
wrap_u_repeat_action->set_checked(true);
|
|
|
|
|
|
|
|
|
|
auto& wrap_t_menu = texture_menu.add_submenu("Wrap &T");
|
|
|
|
|
GUI::ActionGroup wrap_t_actions;
|
|
|
|
|
wrap_t_actions.set_exclusive(true);
|
|
|
|
|
|
|
|
|
|
auto wrap_t_repeat_action = GUI::Action::create_checkable("&Repeat", [&widget](auto&) {
|
|
|
|
|
widget.set_wrap_t_mode(GL_REPEAT);
|
|
|
|
|
});
|
|
|
|
|
auto wrap_t_mirrored_repeat_action = GUI::Action::create_checkable("&Mirrored Repeat", [&widget](auto&) {
|
|
|
|
|
widget.set_wrap_t_mode(GL_MIRRORED_REPEAT);
|
|
|
|
|
});
|
|
|
|
|
auto wrap_t_clamp_action = GUI::Action::create_checkable("&Clamp", [&widget](auto&) {
|
|
|
|
|
widget.set_wrap_t_mode(GL_CLAMP);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
wrap_t_actions.add_action(*wrap_t_repeat_action);
|
|
|
|
|
wrap_t_actions.add_action(*wrap_t_mirrored_repeat_action);
|
|
|
|
|
wrap_t_actions.add_action(*wrap_t_clamp_action);
|
|
|
|
|
|
|
|
|
|
wrap_t_menu.add_action(*wrap_t_repeat_action);
|
|
|
|
|
wrap_t_menu.add_action(*wrap_t_mirrored_repeat_action);
|
|
|
|
|
wrap_t_menu.add_action(*wrap_t_clamp_action);
|
|
|
|
|
|
|
|
|
|
wrap_t_repeat_action->set_checked(true);
|
|
|
|
|
|
|
|
|
|
auto& texture_scale_menu = texture_menu.add_submenu("S&cale");
|
|
|
|
|
GUI::ActionGroup texture_scale_actions;
|
|
|
|
|
texture_scale_actions.set_exclusive(true);
|
|
|
|
|
|
|
|
|
|
auto texture_scale_025_action = GUI::Action::create_checkable("0.25x", [&widget](auto&) {
|
|
|
|
|
widget.set_texture_scale(0.25f);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
auto texture_scale_05_action = GUI::Action::create_checkable("0.5x", [&widget](auto&) {
|
|
|
|
|
widget.set_texture_scale(0.5f);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
auto texture_scale_1_action = GUI::Action::create_checkable("1x", [&widget](auto&) {
|
|
|
|
|
widget.set_texture_scale(1);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
auto texture_scale_2_action = GUI::Action::create_checkable("2x", [&widget](auto&) {
|
|
|
|
|
widget.set_texture_scale(2);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
auto texture_scale_4_action = GUI::Action::create_checkable("4x", [&widget](auto&) {
|
|
|
|
|
widget.set_texture_scale(4);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
texture_scale_actions.add_action(*texture_scale_025_action);
|
|
|
|
|
texture_scale_actions.add_action(*texture_scale_05_action);
|
|
|
|
|
texture_scale_actions.add_action(*texture_scale_1_action);
|
|
|
|
|
texture_scale_actions.add_action(*texture_scale_2_action);
|
|
|
|
|
texture_scale_actions.add_action(*texture_scale_4_action);
|
|
|
|
|
|
|
|
|
|
texture_scale_menu.add_action(*texture_scale_025_action);
|
|
|
|
|
texture_scale_menu.add_action(*texture_scale_05_action);
|
|
|
|
|
texture_scale_menu.add_action(*texture_scale_1_action);
|
|
|
|
|
texture_scale_menu.add_action(*texture_scale_2_action);
|
|
|
|
|
texture_scale_menu.add_action(*texture_scale_4_action);
|
|
|
|
|
|
|
|
|
|
texture_scale_1_action->set_checked(true);
|
|
|
|
|
|
2021-08-12 15:39:32 -03:00
|
|
|
auto& texture_mag_filter_menu = texture_menu.add_submenu("Mag Filter");
|
|
|
|
|
GUI::ActionGroup texture_mag_filter_actions;
|
|
|
|
|
texture_mag_filter_actions.set_exclusive(true);
|
|
|
|
|
|
|
|
|
|
auto texture_mag_filter_nearest_action = GUI::Action::create_checkable("&Nearest", [&widget](auto&) {
|
|
|
|
|
widget.set_mag_filter(GL_NEAREST);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
auto texture_mag_filter_linear_action = GUI::Action::create_checkable("&Linear", [&widget](auto&) {
|
|
|
|
|
widget.set_mag_filter(GL_LINEAR);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
texture_mag_filter_actions.add_action(*texture_mag_filter_nearest_action);
|
|
|
|
|
texture_mag_filter_actions.add_action(*texture_mag_filter_linear_action);
|
|
|
|
|
|
|
|
|
|
texture_mag_filter_menu.add_action(*texture_mag_filter_nearest_action);
|
|
|
|
|
texture_mag_filter_menu.add_action(*texture_mag_filter_linear_action);
|
|
|
|
|
|
|
|
|
|
texture_mag_filter_nearest_action->set_checked(true);
|
|
|
|
|
|
2021-07-21 16:21:03 -03:00
|
|
|
auto& help_menu = window->add_menu("&Help");
|
2021-05-19 14:57:59 -03:00
|
|
|
help_menu.add_action(GUI::CommonActions::make_about_action("3D File Viewer", app_icon, window));
|
2021-05-17 18:53:54 -03:00
|
|
|
|
2021-05-02 11:19:50 -03:00
|
|
|
window->show();
|
|
|
|
|
|
2021-05-20 16:50:53 -03:00
|
|
|
auto filename = argc > 1 ? argv[1] : "/home/anon/Documents/3D Models/teapot.obj";
|
2021-08-18 10:33:09 -03:00
|
|
|
if (widget.load_path(filename)) {
|
|
|
|
|
auto canonical_path = Core::File::absolute_path(filename);
|
|
|
|
|
window->set_title(String::formatted("{} - 3D File Viewer", canonical_path));
|
|
|
|
|
}
|
2021-05-20 16:50:53 -03:00
|
|
|
|
2021-05-02 11:19:50 -03:00
|
|
|
return app->exec();
|
|
|
|
|
}
|