Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,13 @@ apt-get install entt-devel libglfw3-devel libGLEW-devel libglm-devel libpng-deve

```sh
sudo apt install libglfw3 libglfw3-dev libglew-dev libglm-dev libpng-dev libopenal-dev libluajit-5.1-dev libvorbis-dev libcurl4-openssl-dev libssl-dev libfreetype6-dev

#### Wayland (optional, enabled by default on Linux)

The native Wayland backend needs `wayland-client`, `wayland-egl`,
`wayland-scanner`, `wayland-protocols`, `libxkbcommon` and EGL. Build with
`-DVOXELENGINE_WAYLAND=OFF` to fall back to GLFW; `libsystemd` (sd-bus) is
picked up automatically for the desktop button layout.
```

> [!TIP]
Expand Down
16 changes: 16 additions & 0 deletions res/layouts/pages/settings_display.xml.lua
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,22 @@ function on_open()
"</select>", app.get_setting("display.window-mode"))
)

-- these only affect the window frame the native wayland backend draws
if app.get_window_backend() == "wayland" then
create_checkbox(
"display.compact-window-bar", "Narrow Window Bar",
"display.compact-window-bar.tooltip"
)
create_checkbox(
"display.simple-window", "Simple Window",
"display.simple-window.tooltip"
)
create_checkbox(
"display.live-resize", "Live Window Resize",
"display.live-resize.tooltip"
)
end

create_checkbox("camera.shaking", "Camera Shaking")
create_checkbox("camera.inertia", "Camera Inertia")
create_checkbox("camera.fov-effects", "Camera FOV Effects")
Expand Down
3 changes: 3 additions & 0 deletions res/preload.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@
"gui/warning",
"gui/error",
"gui/cross",
"gui/minimize_w",
"gui/maximize_w",
"gui/restore_w",
"gui/refresh",
"gui/folder_icon",
"gui/settings_icon",
Expand Down
6 changes: 6 additions & 0 deletions res/texts/ru_RU.txt
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,12 @@ settings.microphone.None=Нет
settings.Clouds quality=Качество облаков
settings.Acoustic effects=Акустические эффекты
settings.Microphone access=Доступ к микрофону
settings.Narrow Window Bar=Узкая шапка окна
settings.Simple Window=Простое окно
settings.Live Window Resize=Живой ресайз окна
settings.display.compact-window-bar.tooltip=Шапка окна высотой 24 px вместо 48 px
settings.display.simple-window.tooltip=Без тени и внешней рамки ресайза
settings.display.live-resize.tooltip=Перерисовывать содержимое окна при изменении размера (выключено — последний кадр растягивается)

# Управление
chunks.reload=Перезагрузить Чанки
Expand Down
Binary file added res/textures/gui/maximize_w.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added res/textures/gui/minimize_w.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added res/textures/gui/restore_w.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
74 changes: 74 additions & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ file(GLOB_RECURSE headers ${CMAKE_CURRENT_SOURCE_DIR}/*.hpp)
file(GLOB_RECURSE sources ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp)
list(REMOVE_ITEM sources ${CMAKE_CURRENT_SOURCE_DIR}/main.cpp)

file(GLOB_RECURSE wayland_sources
${CMAKE_CURRENT_SOURCE_DIR}/window/wayland/*.cpp)
list(REMOVE_ITEM sources ${wayland_sources})

add_library(VoxelEngineSrc STATIC ${sources} ${headers})

find_package(OpenGL REQUIRED)
Expand Down Expand Up @@ -70,6 +74,76 @@ else()
add_library(luajit::luajit ALIAS PkgConfig::luajit)
endif()

option(VOXELENGINE_WAYLAND "Build the native Wayland window backend" ON)
if(CMAKE_SYSTEM_NAME STREQUAL "Linux" AND VOXELENGINE_WAYLAND)
find_package(PkgConfig REQUIRED)
pkg_check_modules(wayland-client IMPORTED_TARGET wayland-client)
pkg_check_modules(xkbcommon IMPORTED_TARGET xkbcommon)
pkg_check_modules(egl IMPORTED_TARGET egl)
find_library(WAYLAND_EGL_LIBRARY NAMES wayland-egl libwayland-egl.so.1)
find_program(WAYLAND_SCANNER wayland-scanner)
pkg_check_modules(libsystemd IMPORTED_TARGET libsystemd)

if(NOT wayland-client_FOUND OR NOT xkbcommon_FOUND OR NOT egl_FOUND
OR NOT WAYLAND_EGL_LIBRARY OR NOT WAYLAND_SCANNER)
message(STATUS "Wayland backend is off: dependencies are not found")
else()
set(WAYLAND_PROTOCOLS ${CMAKE_CURRENT_SOURCE_DIR}/window/wayland/protocols)
set(WAYLAND_GENERATED ${CMAKE_CURRENT_BINARY_DIR}/wayland-protocols)
file(MAKE_DIRECTORY ${WAYLAND_GENERATED})
set(WAYLAND_SOURCES)

function(voxel_wayland_protocol XML NAME)
add_custom_command(
OUTPUT ${WAYLAND_GENERATED}/${NAME}-client-protocol.h
${WAYLAND_GENERATED}/${NAME}-protocol.c
COMMAND ${WAYLAND_SCANNER} client-header
${WAYLAND_PROTOCOLS}/${XML}
${WAYLAND_GENERATED}/${NAME}-client-protocol.h
COMMAND ${WAYLAND_SCANNER} private-code
${WAYLAND_PROTOCOLS}/${XML}
${WAYLAND_GENERATED}/${NAME}-protocol.c
DEPENDS ${WAYLAND_PROTOCOLS}/${XML}
VERBATIM)
set(WAYLAND_SOURCES ${WAYLAND_SOURCES}
${WAYLAND_GENERATED}/${NAME}-protocol.c
PARENT_SCOPE)
endfunction()

voxel_wayland_protocol(xdg-shell.xml xdg-shell)
voxel_wayland_protocol(cursor-shape-v1.xml cursor-shape-v1)
voxel_wayland_protocol(idle-inhibit-unstable-v1.xml
idle-inhibit-unstable-v1)
voxel_wayland_protocol(pointer-constraints-unstable-v1.xml
pointer-constraints-unstable-v1)
voxel_wayland_protocol(relative-pointer-unstable-v1.xml
relative-pointer-unstable-v1)
voxel_wayland_protocol(xdg-decoration-unstable-v1.xml
xdg-decoration-unstable-v1)
# tablet is referenced by cursor-shape-v1
voxel_wayland_protocol(tablet-unstable-v2.xml tablet-unstable-v2)

target_sources(
VoxelEngineSrc PRIVATE ${WAYLAND_SOURCES} ${wayland_sources})
target_include_directories(VoxelEngineSrc PRIVATE ${WAYLAND_GENERATED})
target_compile_definitions(VoxelEngineSrc PRIVATE VOXELENGINE_WAYLAND=1)
if(libsystemd_FOUND)
target_compile_definitions(VoxelEngineSrc PUBLIC VOXELENGINE_SDBUS=1)
target_link_libraries(VoxelEngineSrc PRIVATE PkgConfig::libsystemd)
message(STATUS "Wayland backend: sd-bus settings are on")
else()
message(STATUS "Wayland backend: sd-bus settings are off")
endif()
target_link_libraries(
VoxelEngineSrc
PRIVATE PkgConfig::wayland-client
PkgConfig::xkbcommon
PkgConfig::egl
${WAYLAND_EGL_LIBRARY})
message(STATUS "Wayland backend is on")
endif()
endif()

target_include_directories(VoxelEngineSrc PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})

target_link_libraries(
Expand Down
11 changes: 11 additions & 0 deletions src/engine/Engine.cpp
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
#include "Engine.hpp"
#include "WindowBar.hpp"

#ifndef GLEW_STATIC
#define GLEW_STATIC
#endif

#include <GL/glew.h>

#include "AssetsManagement.hpp"
#include "audio/audio.hpp"
#include "coders/commons.hpp"
Expand Down Expand Up @@ -270,6 +273,7 @@ void Engine::updateFrontend() {
assets->update();
updateHotkeys();
audio::update(delta);
gui->setContentInset(screen->getContentInset(*window));
gui->act(delta, window->getSize());
screen->update(delta);
gui->postAct();
Expand Down Expand Up @@ -309,10 +313,17 @@ void Engine::renderFrame() {
if (input->isCursorLocked() != (gui->getActiveFrame() == nullptr)) {
input->toggleCursor();
}
glViewport(
0,
0,
static_cast<GLsizei>(window->getSize().x),
static_cast<GLsizei>(window->getSize().y)
);
screen->draw(time.getDelta());

DrawContext ctx(nullptr, *window, nullptr);
gui->draw(ctx, *assets->getStorage());
draw_window_bar(*this);
}

void Engine::saveSettings() {
Expand Down
2 changes: 1 addition & 1 deletion src/engine/Mainloop.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ void Mainloop::run() {
engine.applicationTick();
engine.updateFrontend();

if (!window.isIconified()) {
if (!window.isIconified() && window.isFrameRequired()) {
engine.renderFrame();
}
engine.postUpdate();
Expand Down
147 changes: 147 additions & 0 deletions src/engine/WindowBar.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
#include "WindowBar.hpp"

#include "Engine.hpp"
#include "graphics/core/Batch2D.hpp"
#include <GL/glew.h>
#include <glm/gtc/matrix_transform.hpp>
#include "graphics/core/Shader.hpp"
#include "constants.hpp"
#include "graphics/core/Font.hpp"
#include "graphics/core/Texture.hpp"
#include "graphics/ui/GUI.hpp"
#include "window/Window.hpp"
#include "assets/Assets.hpp"

#include <algorithm>
#include <string_view>
#include <codecvt>
#include <locale>

namespace {
std::wstring to_wide(const std::string& text) {
std::wstring_convert<std::codecvt_utf8<wchar_t>> converter;
return converter.from_bytes(text);
}

const std::string BAR_FONT = FONT_DEFAULT;
}

/// @brief Draws the window bar the backend cannot draw itself.
///
/// The bar lives in window coordinates while the GUI container is shifted
/// down by its height in game, so the GUI camera cannot be reused here: the
/// bar sets its own viewport and projection. It borrows the GUI batch only
/// because the ui shader it needs is the one the GUI already loads.
void draw_window_bar(Engine& engine) {
auto& window = engine.getWindow();
const int height = window.getDecorationHeight();
if (height <= 0) {
return;
}
auto* batch = engine.getGUI().getBatch2D();
auto font = engine.requireAssets().getShared<Font>(BAR_FONT);
if (font == nullptr) {
return;
}
const auto size = window.getSize();
const float barHeight = static_cast<float>(height);

glViewport(
0, 0, static_cast<GLsizei>(size.x), static_cast<GLsizei>(size.y)
);
auto shader = engine.requireAssets().get<Shader>("ui");
if (shader != nullptr) {
shader->use();
shader->uniformMatrix(
"u_projview",
glm::ortho(
0.0f,
static_cast<float>(size.x),
static_cast<float>(size.y),
0.0f,
-1.0f,
1.0f
)
);
}

batch->begin();
batch->setColor(glm::vec4(0.0f, 0.0f, 0.0f, 0.82f));
batch->rect(0.0f, 0.0f, size.x, barHeight);

const auto& buttons = window.getDecorationButtons();
const int hovered = window.getDecorationHoveredButton();

float titleLeft = static_cast<float>(barHeight * 0.4f);
float titleRight = size.x - 8.0f;
for (const auto& item : buttons) {
if (item.x + item.width * 0.5f < size.x * 0.5f) {
titleLeft = std::max(titleLeft, item.x + item.width + 8.0f);
} else {
titleRight = std::min(titleRight, item.x - 8.0f);
}
}
const std::wstring title = to_wide(window.getTitle());
const float available = std::max(0.0f, titleRight - titleLeft);
size_t titleLength = title.size();
while (titleLength > 0 && font->calcWidth(title, titleLength) > available) {
titleLength--;
}
if (titleLength > 0) {
const float textWidth = static_cast<float>(
font->calcWidth(title, titleLength)
);
const float centered = (static_cast<float>(size.x) - textWidth) * 0.5f;
const float titleX = std::clamp(
centered,
titleLeft,
std::max(titleLeft, titleRight - textWidth)
);
batch->setColor(glm::vec4(1.0f, 1.0f, 1.0f, 0.85f));
font->draw(
*batch, std::wstring_view(title).substr(0, titleLength),
static_cast<int>(titleX),
(height - font->getLineHeight()) / 2,
nullptr, 0
);
batch->untexture();
}

for (size_t i = 0; i < buttons.size(); i++) {
const auto& item = buttons[i];
const float x = item.x;
const float cx = x + item.width * 0.5f;
const float cy = barHeight * 0.5f;
const bool isHovered = hovered == static_cast<int>(i);
if (isHovered) {
batch->setColor(glm::vec4(1.0f, 1.0f, 1.0f, 0.16f));
const float button = item.width;
batch->rect(
item.x, (barHeight - button) * 0.5f, item.width, button
);
}
const float ink = isHovered ? 1.0f : 0.8f;
batch->setColor(glm::vec4(1.0f, 1.0f, 1.0f, ink));
const char* iconName =
item.button == DecorationButton::CLOSE
? "gui/cross"
: item.button == DecorationButton::MINIMIZE
? "gui/minimize_w"
: window.isMaximized() ? "gui/restore_w"
: "gui/maximize_w";
auto icon = engine.requireAssets().getShared<Texture>(iconName);
if (icon != nullptr) {
const float iconSize = std::min(
16.0f, static_cast<float>(icon->getWidth())
);
batch->texture(icon.get());
batch->rect(
cx - iconSize * 0.5f, cy - iconSize * 0.5f, iconSize,
iconSize, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, ink
);
batch->untexture();
}

}
batch->flush();
}
5 changes: 5 additions & 0 deletions src/engine/WindowBar.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#pragma once

class Engine;

void draw_window_bar(Engine& engine);
8 changes: 5 additions & 3 deletions src/engine/WindowControl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,11 @@ WindowControl::Result WindowControl::initialize() {
throw initialize_error("could not initialize window");
}
window->setFramerate(settings.display.framerate.get());
if (auto icon = load_icon()) {
icon->flipY();
window->setIcon(icon.get());
if (window->isIconSupported()) {
if (auto icon = load_icon()) {
icon->flipY();
window->setIcon(icon.get());
}
}

return Result {std::move(window), std::move(input)};
Expand Down
10 changes: 8 additions & 2 deletions src/frontend/hud.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -625,8 +625,14 @@ void Hud::setDebug(bool flag) {

void Hud::draw(const DrawContext& ctx){
const auto& viewport = ctx.getViewport();
const int inset = gui.getContentInset();
const int contentHeight = std::max(
1, static_cast<int>(viewport.y) - inset
);

updateElementsPosition(viewport);
updateElementsPosition(
glm::uvec2(viewport.x, static_cast<uint>(contentHeight))
);

uicamera->setFov(viewport.y);
uicamera->setAspectRatio(viewport.x / static_cast<float>(viewport.y));
Expand Down Expand Up @@ -698,7 +704,7 @@ void Hud::updateElementsPosition(const glm::uvec2& viewport) {
}
}
if (exchangeSlot != nullptr) {
exchangeSlot->setPos(input.getCursor().pos);
exchangeSlot->setPos(gui.getCursorPosition());
}
hotbarView->setPos(glm::vec2(viewport.x / 2, viewport.y - 65));
hotbarView->setSelected(player.getChosenSlot());
Expand Down
Loading
Loading