Characters: 16:9 "Spielen" screen, autosave interval option
- Shared screen kit (mods/screen, docs/screens.md): a 1365x768 canvas centred in the render width on our own dark background, the game's frames and buttons; the main menu renders full width while it is shown. - The load window (cUI_Savegame) is replaced by the Spielen screen: character list, hero panel, campaign x difficulty grid, save/autosave choice, rename/delete/reset/new character dialogs. - Migrated characters without a shared hero load their hero's world first, then start the new world. - Options, Allgemein: autosave interval (Aus/5/10/15/30 minutes). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
732d9c2abb
commit
34a119433a
16 files changed
+1391
-468
No files matched your search
@@ -0,0 +1,154 @@
|
||||
// 16:9 menu screens (mods/screen.h, docs/screens.md).
|
||||
|
||||
#include "mods/screen.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <mutex>
|
||||
#include <set>
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include "game/ui/button.h"
|
||||
#include "game/ui/control.h"
|
||||
#include "game/ui/draw.h"
|
||||
#include "game/ui/events.h"
|
||||
#include "game/ui/frame.h"
|
||||
#include "game/ui/texts.h"
|
||||
#include "mods/widescreen.h"
|
||||
|
||||
namespace mods::screen {
|
||||
namespace {
|
||||
|
||||
// Background (decided 2026-10-20: our own, no picture): dark, a little warm
|
||||
// at the top, darker towards the bottom and the sides.
|
||||
constexpr uint32_t kBackgroundTop = 0xff1c1712;
|
||||
constexpr uint32_t kBackgroundBottom = 0xff070605;
|
||||
constexpr uint32_t kVignette = 0xa0000000;
|
||||
constexpr int kVignetteWidth = 240;
|
||||
constexpr uint32_t kPanelBackdrop = 0xb4000000;
|
||||
constexpr int kHeadingHeight = 30;
|
||||
|
||||
// Frames drawn so far, to tell a button drawn this frame (events come between
|
||||
// frames).
|
||||
std::atomic<unsigned> g_frame{1};
|
||||
|
||||
// The windows whose screen is shown: the menu thread renders the full width
|
||||
// while any is.
|
||||
std::mutex g_shownLock;
|
||||
std::set<const void*> g_shown;
|
||||
|
||||
void NoteShown(const void* window, bool shown) {
|
||||
std::lock_guard guard(g_shownLock);
|
||||
if (shown) {
|
||||
g_shown.insert(window);
|
||||
} else {
|
||||
g_shown.erase(window);
|
||||
}
|
||||
widescreen::SetWideMenuScreen(!g_shown.empty());
|
||||
}
|
||||
|
||||
void DrawBackground(void* device) {
|
||||
const float width = static_cast<float>(std::max(widescreen::RenderWidth(), kWidth));
|
||||
const float height = static_cast<float>(kHeight);
|
||||
game::FillVertical(device, 0, 0, width, height, kBackgroundTop, kBackgroundBottom);
|
||||
game::FillHorizontal(device, 0, 0, kVignetteWidth, height, kVignette, 0);
|
||||
game::FillHorizontal(device, width - kVignetteWidth, 0, width, height, 0, kVignette);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int Render(void* window, void* device, int (*body)(void* context), void* context) {
|
||||
const bool shown = game::ControlShown(window);
|
||||
NoteShown(window, shown);
|
||||
if (!shown || !device) {
|
||||
return 0;
|
||||
}
|
||||
++g_frame;
|
||||
DrawBackground(device);
|
||||
return hud::RenderCanvas(window, body, context);
|
||||
}
|
||||
|
||||
int Handle(void* event, int (*body)(void* context), void* context) {
|
||||
return hud::HandleCanvas(event, body, context);
|
||||
}
|
||||
|
||||
void Fill(void* device, const UiRect& r, uint32_t colour) {
|
||||
game::FillRect(device, static_cast<float>(r.x), static_cast<float>(r.y), static_cast<float>(r.x + r.w),
|
||||
static_cast<float>(r.y + r.h), colour);
|
||||
}
|
||||
|
||||
void Text(void* device, const UiRect& rect, const std::wstring& text, uint32_t colour, uint32_t flags) {
|
||||
game::DrawText(device, kFont, game::CenterLineVertically(kFont, rect), text.c_str(), colour, flags);
|
||||
}
|
||||
|
||||
UiRect Inset(const UiRect& r, int by) {
|
||||
return {r.x + by, r.y + by, static_cast<int16_t>(r.w - 2 * by), static_cast<int16_t>(r.h - 2 * by)};
|
||||
}
|
||||
|
||||
UiRect Panel::Draw(void* device, const UiRect& rect, const std::wstring& heading) {
|
||||
if (!frame_ || rect.x != rect_.x || rect.y != rect_.y || rect.w != rect_.w || rect.h != rect_.h) {
|
||||
if (frame_) {
|
||||
game::DeleteControl(frame_);
|
||||
}
|
||||
frame_ = game::NewFrame(name_, rect);
|
||||
rect_ = rect;
|
||||
}
|
||||
Fill(device, rect, kPanelBackdrop);
|
||||
if (frame_) {
|
||||
game::ControlRender(frame_, device);
|
||||
}
|
||||
UiRect content = Inset(rect, kPanelPadding);
|
||||
if (!heading.empty()) {
|
||||
Text(device, {content.x, content.y, content.w, kHeadingHeight}, heading, kHeadingColour);
|
||||
content.y += kHeadingHeight;
|
||||
content.h = static_cast<int16_t>(content.h - kHeadingHeight);
|
||||
}
|
||||
return content;
|
||||
}
|
||||
|
||||
void Button::Draw(void* device, const uint8_t* model, const UiRect& rect) {
|
||||
if (!control_) {
|
||||
captionId_ = game::RegisterText(caption_.c_str());
|
||||
control_ = game::NewButtonLike(model, rect, captionId_, name_);
|
||||
if (!control_) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
rect_ = rect;
|
||||
*reinterpret_cast<UiRect*>(control_ + game::kControlRect) = rect;
|
||||
game::ControlRender(control_, device);
|
||||
drawnFrame_ = g_frame;
|
||||
}
|
||||
|
||||
bool Button::Clicked(void* event) {
|
||||
if (!control_ || drawnFrame_ != g_frame) {
|
||||
return false;
|
||||
}
|
||||
game::ControlHandleEvent(control_, event);
|
||||
return game::MouseEventKind(event) == game::MouseEvent::ButtonDown &&
|
||||
game::EventField(event, game::kMouseButton) == game::kLeftButton &&
|
||||
game::Inside(rect_, game::EventField(event, game::kMouseButtonX),
|
||||
game::EventField(event, game::kMouseButtonY));
|
||||
}
|
||||
|
||||
void Button::SetCaption(const std::wstring& caption) {
|
||||
caption_ = caption;
|
||||
if (captionId_) {
|
||||
game::SetTextOverride(captionId_, caption_);
|
||||
}
|
||||
}
|
||||
|
||||
int16_t ButtonHeight(const uint8_t* model) {
|
||||
return reinterpret_cast<const UiRect*>(model + game::kControlRect)->h;
|
||||
}
|
||||
|
||||
bool DoubleClick::Click(int target) {
|
||||
const unsigned long now = GetTickCount();
|
||||
const bool twice = target == last_ && now - at_ <= GetDoubleClickTime();
|
||||
last_ = twice ? -1 : target;
|
||||
at_ = now;
|
||||
return twice;
|
||||
}
|
||||
|
||||
} // namespace mods::screen
|
||||
Reference in new issue
Block a user