Files
MontaukOS/programs/src/login/login_render.cpp
T

203 lines
8.3 KiB
C++

/*
* login_render.cpp
* Layout and drawing for the MontaukOS login screen
* Copyright (c) 2026 Daniel Hammer
*/
#include "login.hpp"
using namespace gui;
static Rect offset_rect(Rect rect, int dx, int dy) {
rect.x += dx;
rect.y += dy;
return rect;
}
LoginLayout layout_login_screen(LoginState* ls) {
LoginLayout lo = {};
int sfh = system_font_height();
lo.window_title = ls->mode == MODE_FIRST_BOOT ? "MontaukOS Setup" : "MontaukOS";
lo.heading = ls->mode == MODE_FIRST_BOOT ? "Create Administrator Account" : "Log In";
lo.subtitle = ls->mode == MODE_FIRST_BOOT
? "Create the first administrator account."
: "Sign in to continue to the desktop.";
lo.submit_label = ls->mode == MODE_FIRST_BOOT ? "Create Account" : "Log In";
lo.content_x = CONTENT_PAD_X;
lo.content_w = CARD_W - CONTENT_PAD_X * 2;
int y = TITLEBAR_H + CONTENT_TOP_PAD;
lo.heading_y = y;
y += sfh + 6;
lo.subtitle_y = y;
y += sfh + 18;
lo.username_field = {lo.content_x, y + sfh + LABEL_GAP, lo.content_w, FIELD_H};
y = lo.username_field.y + FIELD_H + FIELD_GAP;
if (ls->mode == MODE_FIRST_BOOT) {
lo.display_name_field = {lo.content_x, y + sfh + LABEL_GAP, lo.content_w, FIELD_H};
y = lo.display_name_field.y + FIELD_H + FIELD_GAP;
lo.password_field = {lo.content_x, y + sfh + LABEL_GAP, lo.content_w, FIELD_H};
y = lo.password_field.y + FIELD_H + FIELD_GAP;
lo.confirm_field = {lo.content_x, y + sfh + LABEL_GAP, lo.content_w, FIELD_H};
y = lo.confirm_field.y + FIELD_H + FIELD_GAP;
} else {
lo.password_field = {lo.content_x, y + sfh + LABEL_GAP, lo.content_w, FIELD_H};
y = lo.password_field.y + FIELD_H + FIELD_GAP;
}
// Session selector (login mode only): a "Session" label above two radio
// options that pick the desktop or the full-screen console.
if (ls->mode == MODE_LOGIN) {
lo.session_label_y = y;
int ry = y + sfh + LABEL_GAP;
int radio_h = 22;
int half = lo.content_w / 2;
lo.desktop_radio = {lo.content_x, ry, half, radio_h};
lo.console_radio = {lo.content_x + half, ry, half, radio_h};
y = ry + radio_h + FIELD_GAP;
} else {
lo.session_label_y = -1;
}
if (ls->show_error) {
lo.error_y = y;
y += sfh + 12;
} else {
lo.error_y = -1;
}
int footer_y = y + BODY_BOTTOM_PAD;
int card_h = footer_y + FOOTER_H;
int card_x = (ls->screen_w - CARD_W) / 2;
int card_y = (ls->screen_h - card_h) / 2;
lo.card = {card_x, card_y, CARD_W, card_h};
lo.titlebar = {card_x, card_y, CARD_W, TITLEBAR_H};
lo.footer = {card_x, card_y + footer_y, CARD_W, FOOTER_H};
int submit_w = text_width(lo.submit_label) + 32;
if (submit_w < 100) submit_w = 100;
int footer_btn_y = footer_y + (FOOTER_H - BTN_H) / 2;
lo.shutdown_button = {FOOTER_PAD, footer_btn_y, POWER_BTN_W, BTN_H};
lo.reboot_button = {FOOTER_PAD + POWER_BTN_W + POWER_BTN_GAP, footer_btn_y, POWER_BTN_W, BTN_H};
lo.submit_button = {CARD_W - FOOTER_PAD - submit_w, footer_btn_y, submit_w, BTN_H};
lo.username_field = offset_rect(lo.username_field, card_x, card_y);
lo.display_name_field = offset_rect(lo.display_name_field, card_x, card_y);
lo.password_field = offset_rect(lo.password_field, card_x, card_y);
lo.confirm_field = offset_rect(lo.confirm_field, card_x, card_y);
lo.shutdown_button = offset_rect(lo.shutdown_button, card_x, card_y);
lo.reboot_button = offset_rect(lo.reboot_button, card_x, card_y);
lo.submit_button = offset_rect(lo.submit_button, card_x, card_y);
lo.desktop_radio = offset_rect(lo.desktop_radio, card_x, card_y);
lo.console_radio = offset_rect(lo.console_radio, card_x, card_y);
lo.content_x += card_x;
lo.heading_y += card_y;
lo.subtitle_y += card_y;
if (lo.error_y >= 0) lo.error_y += card_y;
if (lo.session_label_y >= 0) lo.session_label_y += card_y;
return lo;
}
void draw_login_screen(LoginState* ls) {
Framebuffer& fb = ls->fb;
LoginLayout lo = layout_login_screen(ls);
const mtk::Theme& th = ls->theme;
int sfh = system_font_height();
int sw = ls->screen_w;
int sh = ls->screen_h;
// MTK widgets draw through a Canvas, which assumes a tightly packed pixel
// buffer (pitch == width*4). Draw straight into the framebuffer back buffer
// when its pitch is already packed; otherwise compose into the packed
// scratch buffer and copy across (handling pitch) before flipping.
bool packed = fb.pitch() == sw * (int)sizeof(uint32_t);
uint32_t* target = packed ? fb.buffer() : ls->compose;
if (!target) return;
Canvas c(target, sw, sh);
// ==== Background ====
if (__atomic_load_n(&ls->has_wallpaper, __ATOMIC_ACQUIRE) && ls->bg_wallpaper) {
montauk::memcpy(target, ls->bg_wallpaper,
(uint64_t)sw * sh * sizeof(uint32_t));
} else {
c.fill(BG_COLOR);
}
// ==== Card ====
int so = 4; // drop-shadow offset
c.fill_rect_alpha(lo.card.x + so, lo.card.y + lo.card.h, lo.card.w, so, colors::SHADOW);
c.fill_rect_alpha(lo.card.x + lo.card.w, lo.card.y + so, so, lo.card.h, colors::SHADOW);
c.fill_rect_alpha(lo.card.x + lo.card.w, lo.card.y + lo.card.h, so, so, colors::SHADOW);
c.fill_rect(lo.card.x, lo.card.y, lo.card.w, lo.card.h, CARD_BG);
c.fill_rect(lo.titlebar.x, lo.titlebar.y, lo.titlebar.w, lo.titlebar.h, TITLEBAR_BG);
c.fill_rect(lo.footer.x, lo.footer.y, lo.footer.w, lo.footer.h, FOOTER_BG);
c.rect(lo.card.x, lo.card.y, lo.card.w, lo.card.h, CARD_BORDER);
c.hline(lo.titlebar.x, lo.titlebar.y + lo.titlebar.h - 1, lo.titlebar.w, CARD_BORDER);
c.hline(lo.footer.x, lo.footer.y, lo.footer.w, CARD_BORDER);
int window_tw = text_width(lo.window_title);
c.text(lo.titlebar.x + (lo.titlebar.w - window_tw) / 2,
lo.titlebar.y + (TITLEBAR_H - sfh) / 2, lo.window_title, th.text);
c.text(lo.content_x, lo.heading_y, lo.heading, th.text);
c.text(lo.content_x, lo.subtitle_y, lo.subtitle, th.text_subtle);
// ==== Fields ====
auto field = [&](const Rect& r, const char* label, const char* val,
mtk::TextInputState& st, bool masked, bool active) {
c.text(r.x, r.y - LABEL_GAP - sfh, label, th.text_muted);
mtk::draw_text_field(c, r, val, st.cursor, active, masked, th,
active ? st.selection_anchor : -1);
};
field(lo.username_field, "Username", ls->username, ls->username_input,
false, ls->active_field == 0);
if (ls->mode == MODE_FIRST_BOOT) {
field(lo.display_name_field, "Display Name", ls->display_name,
ls->display_name_input, false, ls->active_field == 1);
field(lo.password_field, "Password", ls->password,
ls->password_input, true, ls->active_field == 2);
field(lo.confirm_field, "Confirm Password", ls->confirm,
ls->confirm_input, true, ls->active_field == 3);
} else {
field(lo.password_field, "Password", ls->password,
ls->password_input, true, ls->active_field == 1);
}
// ==== Session selector ====
if (ls->mode == MODE_LOGIN && lo.session_label_y >= 0) {
c.text(lo.content_x, lo.session_label_y, "Session", th.text_muted);
mtk::draw_radio(c, lo.desktop_radio, "Desktop",
ls->session_mode == SESSION_DESKTOP, th);
mtk::draw_radio(c, lo.console_radio, "Console",
ls->session_mode == SESSION_CONSOLE, th);
}
if (ls->show_error && lo.error_y >= 0)
c.text(lo.content_x, lo.error_y, ls->error_msg, th.danger);
// ==== Buttons ====
auto btn = [&](const Rect& r) {
return mtk::widget_state(false, r.contains(ls->mouse.x, ls->mouse.y), true);
};
mtk::draw_button(c, lo.shutdown_button, "Shut Down", mtk::BUTTON_SECONDARY,
btn(lo.shutdown_button), th);
mtk::draw_button(c, lo.reboot_button, "Restart", mtk::BUTTON_SECONDARY,
btn(lo.reboot_button), th);
mtk::draw_button(c, lo.submit_button, lo.submit_label, mtk::BUTTON_PRIMARY,
btn(lo.submit_button), th);
// ==== Present ====
if (!packed) fb.copy_from(ls->compose, sw * (int)sizeof(uint32_t));
draw_cursor(fb, ls->mouse.x, ls->mouse.y);
fb.flip();
}