Squash commits for public release
This commit is contained in:
5
userland/applications/about/.info.mk
Normal file
5
userland/applications/about/.info.mk
Normal file
@@ -0,0 +1,5 @@
|
||||
APPS += ABOUT
|
||||
|
||||
ABOUT_NAME = about
|
||||
ABOUT_LIBS = cxx ui
|
||||
ABOUT_INSTALL_PATH = bin/
|
||||
64
userland/applications/about/AboutLineView.h
Normal file
64
userland/applications/about/AboutLineView.h
Normal file
@@ -0,0 +1,64 @@
|
||||
#pragma once
|
||||
#include <libg/Font.h>
|
||||
#include <libui/Label.h>
|
||||
#include <libui/View.h>
|
||||
#include <string>
|
||||
|
||||
class AboutLineView : public UI::View {
|
||||
UI_OBJECT();
|
||||
|
||||
public:
|
||||
AboutLineView(UI::View* superview, const LG::Rect& frame, std::string title, std::string content)
|
||||
: UI::View(superview, frame)
|
||||
, m_title(title)
|
||||
, m_content(content)
|
||||
{
|
||||
const int spacing = 4;
|
||||
set_background_color(LG::Color::LightSystemBackground);
|
||||
|
||||
auto& label = add_subview<UI::Label>(LG::Rect(0, 0, 16, 16));
|
||||
label.set_text_color(LG::Color::DarkSystemText);
|
||||
label.set_text(m_title);
|
||||
label.set_width(label.preferred_width());
|
||||
|
||||
auto& target_label = add_subview<UI::Label>(LG::Rect(0, 0, 16, 16));
|
||||
target_label.set_text_color(LG::Color::DarkSystemText);
|
||||
target_label.set_text(m_content);
|
||||
target_label.set_width(target_label.preferred_width());
|
||||
|
||||
add_constraint(UI::Constraint(target_label, UI::Constraint::Attribute::Left, UI::Constraint::Relation::Equal, label, UI::Constraint::Attribute::Right, 1, spacing));
|
||||
add_constraint(UI::Constraint(target_label, UI::Constraint::Attribute::Top, UI::Constraint::Relation::Equal, label, UI::Constraint::Attribute::Top, 1, 0));
|
||||
|
||||
set_width(label.preferred_width() + spacing + target_label.preferred_width());
|
||||
}
|
||||
|
||||
AboutLineView(UI::View* superview, UI::Window* window, const LG::Rect& frame, std::string title, std::string content)
|
||||
: UI::View(superview, window, frame)
|
||||
, m_title(title)
|
||||
, m_content(content)
|
||||
{
|
||||
const int spacing = 4;
|
||||
set_background_color(LG::Color::LightSystemBackground);
|
||||
|
||||
auto& label = add_subview<UI::Label>(LG::Rect(0, 0, 16, 16));
|
||||
label.set_text_color(LG::Color::DarkSystemText);
|
||||
label.set_text(m_title);
|
||||
label.set_width(label.preferred_width());
|
||||
|
||||
auto& target_label = add_subview<UI::Label>(LG::Rect(0, 0, 16, 16));
|
||||
target_label.set_text_color(LG::Color::DarkSystemText);
|
||||
target_label.set_text(m_content);
|
||||
target_label.set_width(target_label.preferred_width());
|
||||
|
||||
add_constraint(UI::Constraint(target_label, UI::Constraint::Attribute::Left, UI::Constraint::Relation::Equal, label, UI::Constraint::Attribute::Right, 1, spacing));
|
||||
add_constraint(UI::Constraint(target_label, UI::Constraint::Attribute::Top, UI::Constraint::Relation::Equal, label, UI::Constraint::Attribute::Top, 1, 0));
|
||||
|
||||
set_width(label.preferred_width() + spacing + target_label.preferred_width());
|
||||
}
|
||||
|
||||
~AboutLineView() = default;
|
||||
|
||||
private:
|
||||
std::string m_title {};
|
||||
std::string m_content {};
|
||||
};
|
||||
29
userland/applications/about/AppDelegate.cpp
Normal file
29
userland/applications/about/AppDelegate.cpp
Normal file
@@ -0,0 +1,29 @@
|
||||
#include "ViewController.h"
|
||||
#include <libui/AppDelegate.h>
|
||||
#include <libui/MenuBar.h>
|
||||
|
||||
class AppDelegate : public UI::AppDelegate {
|
||||
public:
|
||||
AppDelegate() = default;
|
||||
virtual ~AppDelegate() = default;
|
||||
|
||||
LG::Size preferred_desktop_window_size() const override { return LG::Size(200, 210); }
|
||||
const char* icon_path() const override { return "/res/icons/apps/about.icon"; }
|
||||
|
||||
virtual bool application() override
|
||||
{
|
||||
auto style = StatusBarStyle(LG::Color(231, 240, 250)).set_hide_text();
|
||||
auto& window = std::xos::construct<UI::Window>("About", window_size(), icon_path(), style);
|
||||
auto& superview = window.create_superview<UI::View, ViewController>();
|
||||
|
||||
auto demo_menu = UI::Menu("Demo");
|
||||
demo_menu.add_item(UI::MenuItem("Say hello", [] { Logger::debug << "Hello!" << std::endl; }));
|
||||
window.menubar().add_menu(std::move(demo_menu));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
};
|
||||
|
||||
SET_APP_DELEGATE(AppDelegate);
|
||||
17
userland/applications/about/BUILD.gn
Normal file
17
userland/applications/about/BUILD.gn
Normal file
@@ -0,0 +1,17 @@
|
||||
import("//build/userland/TEMPLATE.gni")
|
||||
|
||||
xOS_application("about") {
|
||||
display_name = "About"
|
||||
sources = [
|
||||
"AppDelegate.cpp",
|
||||
]
|
||||
configs = [
|
||||
"//build/userland:userland_flags",
|
||||
]
|
||||
deplibs = [
|
||||
"libcxx",
|
||||
"libfoundation",
|
||||
"libg",
|
||||
"libui"
|
||||
]
|
||||
}
|
||||
78
userland/applications/about/ViewController.h
Normal file
78
userland/applications/about/ViewController.h
Normal file
@@ -0,0 +1,78 @@
|
||||
#pragma once
|
||||
#include "AboutLineView.h"
|
||||
#include <libui/App.h>
|
||||
#include <libui/Button.h>
|
||||
#include <libui/Label.h>
|
||||
#include <libui/View.h>
|
||||
#include <libui/ViewController.h>
|
||||
#include <libui/Window.h>
|
||||
#include <memory>
|
||||
#include <sys/types.h>
|
||||
#include <sys/utsname.h>
|
||||
|
||||
class ViewController : public UI::ViewController<UI::View> {
|
||||
public:
|
||||
ViewController(UI::View& view)
|
||||
: UI::ViewController<UI::View>(view)
|
||||
{
|
||||
}
|
||||
virtual ~ViewController() = default;
|
||||
|
||||
void view_did_load() override
|
||||
{
|
||||
view().set_background_color(LG::Color::LightSystemBackground);
|
||||
|
||||
utsname_t uts;
|
||||
int rc = uname(&uts);
|
||||
|
||||
auto& header = view().add_subview<UI::View>(LG::Rect(0, 0, 0, 0));
|
||||
header.set_background_color(LG::Color(231, 240, 250));
|
||||
|
||||
auto& label = header.add_subview<UI::Label>(LG::Rect(0, 0, 16, 22));
|
||||
label.set_text("About");
|
||||
label.set_text_color(LG::Color(35, 70, 106));
|
||||
label.set_font(LG::Font::system_bold_font(LG::Font::SystemTitleSize));
|
||||
label.set_width(label.preferred_width());
|
||||
|
||||
auto& name_label = view().add_subview<AboutLineView>(LG::Rect(0, 0, 16, 16), "Name:", uts.sysname);
|
||||
auto& cpu_label = view().add_subview<AboutLineView>(LG::Rect(0, 0, 16, 16), "CPU:", uts.machine);
|
||||
auto& version_label = view().add_subview<AboutLineView>(LG::Rect(0, 0, 16, 16), "Version:", uts.release);
|
||||
|
||||
auto& button = view().add_subview<UI::Button>(LG::Rect(0, 0, 10, 10));
|
||||
button.set_background_color(LG::Color::LightSystemButton);
|
||||
button.set_title("System info");
|
||||
button.set_title_color(LG::Color::DarkSystemText);
|
||||
|
||||
auto& footer = view().add_subview<UI::Label>(LG::Rect(0, 0, 16, 16));
|
||||
footer.set_text_color(LG::Color::DarkSystemText);
|
||||
footer.set_text("(c) 2020-2023");
|
||||
footer.set_width(footer.preferred_width());
|
||||
|
||||
view().add_constraint(UI::Constraint(header, UI::Constraint::Attribute::Left, UI::Constraint::Relation::Equal, 0));
|
||||
view().add_constraint(UI::Constraint(header, UI::Constraint::Attribute::Top, UI::Constraint::Relation::Equal, 0));
|
||||
view().add_constraint(UI::Constraint(header, UI::Constraint::Attribute::Right, UI::Constraint::Relation::Equal, 0));
|
||||
view().add_constraint(UI::Constraint(header, UI::Constraint::Attribute::Height, UI::Constraint::Relation::Equal, 60));
|
||||
|
||||
view().add_constraint(UI::Constraint(label, UI::Constraint::Attribute::Left, UI::Constraint::Relation::Equal, UI::SafeArea::Left));
|
||||
view().add_constraint(UI::Constraint(label, UI::Constraint::Attribute::CenterY, UI::Constraint::Relation::Equal, header, UI::Constraint::Attribute::CenterY, 1, 0));
|
||||
|
||||
view().add_constraint(UI::Constraint(name_label, UI::Constraint::Attribute::Left, UI::Constraint::Relation::Equal, UI::SafeArea::Left));
|
||||
view().add_constraint(UI::Constraint(name_label, UI::Constraint::Attribute::Top, UI::Constraint::Relation::Equal, header, UI::Constraint::Attribute::Bottom, 1, UI::Padding::AfterTitle));
|
||||
|
||||
view().add_constraint(UI::Constraint(cpu_label, UI::Constraint::Attribute::Left, UI::Constraint::Relation::Equal, UI::SafeArea::Left));
|
||||
view().add_constraint(UI::Constraint(cpu_label, UI::Constraint::Attribute::Top, UI::Constraint::Relation::Equal, name_label, UI::Constraint::Attribute::Bottom, 1, 4));
|
||||
|
||||
view().add_constraint(UI::Constraint(version_label, UI::Constraint::Attribute::Left, UI::Constraint::Relation::Equal, UI::SafeArea::Left));
|
||||
view().add_constraint(UI::Constraint(version_label, UI::Constraint::Attribute::Top, UI::Constraint::Relation::Equal, cpu_label, UI::Constraint::Attribute::Bottom, 1, 4));
|
||||
|
||||
view().add_constraint(UI::Constraint(button, UI::Constraint::Attribute::Left, UI::Constraint::Relation::Equal, UI::SafeArea::Left));
|
||||
view().add_constraint(UI::Constraint(button, UI::Constraint::Attribute::Top, UI::Constraint::Relation::Equal, version_label, UI::Constraint::Attribute::Bottom, 1, 8));
|
||||
|
||||
view().add_constraint(UI::Constraint(footer, UI::Constraint::Attribute::CenterX, UI::Constraint::Relation::Equal, view(), UI::Constraint::Attribute::CenterX, 1, 0));
|
||||
view().add_constraint(UI::Constraint(footer, UI::Constraint::Attribute::Bottom, UI::Constraint::Relation::Equal, view(), UI::Constraint::Attribute::Bottom, 1, -UI::SafeArea::Bottom));
|
||||
|
||||
view().set_needs_layout();
|
||||
}
|
||||
|
||||
private:
|
||||
};
|
||||
23
userland/applications/activity_monitor/AppDelegate.cpp
Normal file
23
userland/applications/activity_monitor/AppDelegate.cpp
Normal file
@@ -0,0 +1,23 @@
|
||||
#include "ViewController.h"
|
||||
#include <libui/AppDelegate.h>
|
||||
|
||||
class AppDelegate : public UI::AppDelegate {
|
||||
public:
|
||||
AppDelegate() = default;
|
||||
virtual ~AppDelegate() = default;
|
||||
|
||||
LG::Size preferred_desktop_window_size() const override { return LG::Size(220, 210); }
|
||||
const char* icon_path() const override { return "/res/icons/apps/activity_monitor.icon"; }
|
||||
|
||||
virtual bool application() override
|
||||
{
|
||||
auto style = StatusBarStyle(LG::Color(222, 232, 227)).set_hide_text();
|
||||
auto& window = std::xos::construct<UI::Window>("Monitor", window_size(), icon_path(), style);
|
||||
auto& superview = window.create_superview<UI::View, ViewController>();
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
};
|
||||
|
||||
SET_APP_DELEGATE(AppDelegate);
|
||||
16
userland/applications/activity_monitor/BUILD.gn
Normal file
16
userland/applications/activity_monitor/BUILD.gn
Normal file
@@ -0,0 +1,16 @@
|
||||
import("//build/userland/TEMPLATE.gni")
|
||||
|
||||
xOS_application("activity_monitor") {
|
||||
display_name = "Monitor"
|
||||
sources = [
|
||||
"AppDelegate.cpp",
|
||||
"GraphView.cpp",
|
||||
]
|
||||
configs = [ "//build/userland:userland_flags" ]
|
||||
deplibs = [
|
||||
"libcxx",
|
||||
"libfoundation",
|
||||
"libg",
|
||||
"libui",
|
||||
]
|
||||
}
|
||||
40
userland/applications/activity_monitor/GraphView.cpp
Normal file
40
userland/applications/activity_monitor/GraphView.cpp
Normal file
@@ -0,0 +1,40 @@
|
||||
#include "GraphView.h"
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
#include <libfoundation/EventLoop.h>
|
||||
#include <libfoundation/KeyboardMapping.h>
|
||||
#include <libg/Color.h>
|
||||
#include <libui/Context.h>
|
||||
|
||||
GraphView::GraphView(UI::View* superview, const LG::Rect& frame, int data_size)
|
||||
: UI::View(superview, frame)
|
||||
, m_data(data_size)
|
||||
{
|
||||
for (int i = 0; i < data_size; i++) {
|
||||
m_data.push_back(0);
|
||||
}
|
||||
}
|
||||
|
||||
void GraphView::display(const LG::Rect& rect)
|
||||
{
|
||||
LG::Context ctx = UI::graphics_current_context();
|
||||
ctx.add_clip(rect);
|
||||
|
||||
ctx.set_fill_color(LG::Color(233, 233, 233));
|
||||
ctx.fill(bounds());
|
||||
|
||||
ctx.set_fill_color(LG::Color(14, 72, 19));
|
||||
|
||||
size_t left_padding = bounds().width();
|
||||
size_t height = bounds().height();
|
||||
size_t column_width = 3;
|
||||
|
||||
for (int i = m_data.size() - 1; i >= 0; i--) {
|
||||
left_padding -= column_width;
|
||||
size_t column_height = (m_data[i] * height) / 100;
|
||||
ctx.fill(LG::Rect(left_padding, height - column_height, column_width, column_height));
|
||||
if (left_padding < column_width) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
25
userland/applications/activity_monitor/GraphView.h
Normal file
25
userland/applications/activity_monitor/GraphView.h
Normal file
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
#include <libg/Font.h>
|
||||
#include <libui/View.h>
|
||||
#include <string>
|
||||
|
||||
class GraphView : public UI::View {
|
||||
UI_OBJECT();
|
||||
|
||||
public:
|
||||
GraphView(UI::View* superview, const LG::Rect&, int data_size);
|
||||
|
||||
void display(const LG::Rect& rect) override;
|
||||
|
||||
void add_new_value(int val)
|
||||
{
|
||||
for (int i = 0; i < m_data.size() - 1; i++) {
|
||||
m_data[i] = m_data[i + 1];
|
||||
}
|
||||
m_data[m_data.size() - 1] = val;
|
||||
set_needs_display();
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<int> m_data;
|
||||
};
|
||||
150
userland/applications/activity_monitor/ViewController.h
Normal file
150
userland/applications/activity_monitor/ViewController.h
Normal file
@@ -0,0 +1,150 @@
|
||||
#pragma once
|
||||
#include "GraphView.h"
|
||||
#include <libfoundation/ProcessInfo.h>
|
||||
#include <libui/App.h>
|
||||
#include <libui/Button.h>
|
||||
#include <libui/Label.h>
|
||||
#include <libui/StackView.h>
|
||||
#include <libui/View.h>
|
||||
#include <libui/ViewController.h>
|
||||
#include <libui/Window.h>
|
||||
#include <memory>
|
||||
#include <sys/types.h>
|
||||
|
||||
static char buf[256];
|
||||
|
||||
class ViewController : public UI::ViewController<UI::View> {
|
||||
public:
|
||||
ViewController(UI::View& view)
|
||||
: UI::ViewController<UI::View>(view)
|
||||
, m_cpu_count(LFoundation::ProcessInfo::the().processor_count())
|
||||
{
|
||||
}
|
||||
virtual ~ViewController() = default;
|
||||
|
||||
inline int cpu_count() const { return m_cpu_count; }
|
||||
|
||||
void view_did_load() override
|
||||
{
|
||||
state.cpu_load.resize(cpu_count());
|
||||
state.cpu_old_user_time.resize(cpu_count());
|
||||
state.cpu_old_system_time.resize(cpu_count());
|
||||
state.cpu_old_idle_time.resize(cpu_count());
|
||||
|
||||
view().set_background_color(LG::Color::LightSystemBackground);
|
||||
|
||||
auto& header = view().add_subview<UI::View>(LG::Rect(0, 0, 0, 0));
|
||||
header.set_background_color(LG::Color(222, 232, 227));
|
||||
|
||||
auto& label = header.add_subview<UI::Label>(LG::Rect(0, 0, 16, 22));
|
||||
label.set_text_color(LG::Color(14, 72, 19));
|
||||
label.set_text("Monitor");
|
||||
label.set_font(LG::Font::system_bold_font(LG::Font::SystemTitleSize));
|
||||
label.set_width(label.preferred_width());
|
||||
|
||||
auto& switch_tab = view().add_subview<UI::StackView>(LG::Rect(0, 0, 102, 18));
|
||||
switch_tab.set_spacing(8);
|
||||
switch_tab.layer().set_corner_mask(LG::CornerMask(4));
|
||||
switch_tab.layer().set_shading(LG::Shading(LG::Shading::Box));
|
||||
switch_tab.set_spacing(4);
|
||||
|
||||
auto& cpu_tab = switch_tab.add_arranged_subview<UI::Button>();
|
||||
cpu_tab.set_content_edge_insets(UI::EdgeInsets(4, 10, 4, 10));
|
||||
cpu_tab.set_background_color(LG::Color(248, 250, 231));
|
||||
cpu_tab.set_title("CPU");
|
||||
cpu_tab.set_title_color(LG::Color::DarkSystemText);
|
||||
|
||||
auto& mem_tab = switch_tab.add_arranged_subview<UI::Button>();
|
||||
mem_tab.set_content_edge_insets(UI::EdgeInsets(4, 10, 4, 10));
|
||||
mem_tab.set_background_color(LG::Color::White);
|
||||
mem_tab.set_title("Memory");
|
||||
mem_tab.set_title_color(LG::Color::DarkSystemText);
|
||||
|
||||
auto& cpu_label = view().add_subview<UI::Label>(LG::Rect(0, 0, 180, 16));
|
||||
auto& cpu_graphs_stackview = view().add_subview<UI::StackView>(LG::Rect(0, 0, 184, 100));
|
||||
cpu_graphs_stackview.set_distribution(UI::StackView::Distribution::FillEqually);
|
||||
cpu_graphs_stackview.set_spacing(10);
|
||||
|
||||
view().add_constraint(UI::Constraint(header, UI::Constraint::Attribute::Left, UI::Constraint::Relation::Equal, 0));
|
||||
view().add_constraint(UI::Constraint(header, UI::Constraint::Attribute::Top, UI::Constraint::Relation::Equal, 0));
|
||||
view().add_constraint(UI::Constraint(header, UI::Constraint::Attribute::Right, UI::Constraint::Relation::Equal, 0));
|
||||
view().add_constraint(UI::Constraint(header, UI::Constraint::Attribute::Height, UI::Constraint::Relation::Equal, 60));
|
||||
|
||||
view().add_constraint(UI::Constraint(label, UI::Constraint::Attribute::Left, UI::Constraint::Relation::Equal, UI::SafeArea::Left));
|
||||
view().add_constraint(UI::Constraint(label, UI::Constraint::Attribute::CenterY, UI::Constraint::Relation::Equal, header, UI::Constraint::Attribute::CenterY, 1, 0));
|
||||
|
||||
view().add_constraint(UI::Constraint(switch_tab, UI::Constraint::Attribute::Left, UI::Constraint::Relation::Equal, UI::SafeArea::Left));
|
||||
view().add_constraint(UI::Constraint(switch_tab, UI::Constraint::Attribute::CenterY, UI::Constraint::Relation::Equal, header, UI::Constraint::Attribute::Bottom, 1, 0));
|
||||
|
||||
view().add_constraint(UI::Constraint(cpu_label, UI::Constraint::Attribute::Left, UI::Constraint::Relation::Equal, UI::SafeArea::Left));
|
||||
view().add_constraint(UI::Constraint(cpu_label, UI::Constraint::Attribute::Bottom, UI::Constraint::Relation::Equal, UI::SafeArea::Bottom));
|
||||
|
||||
view().add_constraint(UI::Constraint(cpu_graphs_stackview, UI::Constraint::Attribute::Left, UI::Constraint::Relation::Equal, UI::SafeArea::Left));
|
||||
view().add_constraint(UI::Constraint(cpu_graphs_stackview, UI::Constraint::Attribute::Right, UI::Constraint::Relation::Equal, UI::SafeArea::Right));
|
||||
view().add_constraint(UI::Constraint(cpu_graphs_stackview, UI::Constraint::Attribute::Top, UI::Constraint::Relation::Equal, switch_tab, UI::Constraint::Attribute::Bottom, 1, 8));
|
||||
view().add_constraint(UI::Constraint(cpu_graphs_stackview, UI::Constraint::Attribute::Bottom, UI::Constraint::Relation::Equal, cpu_label, UI::Constraint::Attribute::Top, 1, -8));
|
||||
|
||||
for (int i = 0; i < cpu_count(); i++) {
|
||||
auto& graph = cpu_graphs_stackview.add_arranged_subview<GraphView>(200);
|
||||
cpu_graphs_stackview.add_constraint(UI::Constraint(graph, UI::Constraint::Attribute::Height, UI::Constraint::Relation::Equal, cpu_graphs_stackview, UI::Constraint::Attribute::Height, 1, 0));
|
||||
cpu_graphs.push_back(&graph);
|
||||
}
|
||||
|
||||
view().set_needs_layout();
|
||||
|
||||
UI::App::the().event_loop().add(LFoundation::Timer([&] {
|
||||
update_data();
|
||||
cpu_label.set_text(std::string("Load ") + std::to_string(state.cpu_load[0]) + "%");
|
||||
cpu_label.set_needs_display();
|
||||
},
|
||||
1000, LFoundation::Timer::Repeat));
|
||||
}
|
||||
|
||||
int update_cpu_load()
|
||||
{
|
||||
int fd_proc_stat = open("/proc/stat", O_RDONLY);
|
||||
int offset = 0;
|
||||
read(fd_proc_stat, buf, sizeof(buf));
|
||||
|
||||
for (int i = 0; i < cpu_count(); i++) {
|
||||
int user_time, system_time, idle_time;
|
||||
int num;
|
||||
offset += sscanf(buf + offset, "cpu%d %d 0 %d %d\n", &num, &user_time, &system_time, &idle_time);
|
||||
int diff_user_time = user_time - state.cpu_old_user_time[i];
|
||||
int diff_system_time = system_time - state.cpu_old_system_time[i];
|
||||
int diff_idle_time = idle_time - state.cpu_old_idle_time[i];
|
||||
state.cpu_old_user_time[i] = user_time;
|
||||
state.cpu_old_system_time[i] = system_time;
|
||||
state.cpu_old_idle_time[i] = idle_time;
|
||||
|
||||
if (diff_user_time + diff_system_time + diff_idle_time == 0) {
|
||||
state.cpu_load[i] = 0;
|
||||
} else {
|
||||
state.cpu_load[i] = (diff_user_time + diff_system_time) * 100 / (diff_user_time + diff_system_time + diff_idle_time);
|
||||
}
|
||||
|
||||
cpu_graphs[i]->add_new_value(state.cpu_load[i]);
|
||||
}
|
||||
|
||||
close(fd_proc_stat);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void update_data()
|
||||
{
|
||||
update_cpu_load();
|
||||
}
|
||||
|
||||
private:
|
||||
int m_cpu_count;
|
||||
int fd_proc_stat;
|
||||
std::vector<GraphView*> cpu_graphs;
|
||||
|
||||
struct State {
|
||||
std::vector<int> cpu_load;
|
||||
std::vector<int> cpu_old_user_time;
|
||||
std::vector<int> cpu_old_system_time;
|
||||
std::vector<int> cpu_old_idle_time;
|
||||
};
|
||||
State state;
|
||||
};
|
||||
5
userland/applications/calculator/.info.mk
Normal file
5
userland/applications/calculator/.info.mk
Normal file
@@ -0,0 +1,5 @@
|
||||
APPS += CALCULATOR
|
||||
|
||||
ABOUT_NAME = calculator
|
||||
ABOUT_LIBS = cxx ui
|
||||
ABOUT_INSTALL_PATH = bin/
|
||||
22
userland/applications/calculator/AppDelegate.cpp
Normal file
22
userland/applications/calculator/AppDelegate.cpp
Normal file
@@ -0,0 +1,22 @@
|
||||
#include "ViewController.h"
|
||||
#include <libui/AppDelegate.h>
|
||||
|
||||
class AppDelegate : public UI::AppDelegate {
|
||||
public:
|
||||
AppDelegate() = default;
|
||||
virtual ~AppDelegate() = default;
|
||||
|
||||
LG::Size preferred_desktop_window_size() const override { return LG::Size(240, 340); }
|
||||
const char* icon_path() const override { return "/res/icons/apps/calculator.icon"; }
|
||||
|
||||
virtual bool application() override
|
||||
{
|
||||
auto& window = std::xos::construct<UI::Window>("Calculator", window_size(), icon_path());
|
||||
auto& superview = window.create_superview<UI::View, ViewController>();
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
};
|
||||
|
||||
SET_APP_DELEGATE(AppDelegate);
|
||||
17
userland/applications/calculator/BUILD.gn
Normal file
17
userland/applications/calculator/BUILD.gn
Normal file
@@ -0,0 +1,17 @@
|
||||
import("//build/userland/TEMPLATE.gni")
|
||||
|
||||
xOS_application("calculator") {
|
||||
display_name = "Calculator"
|
||||
sources = [
|
||||
"AppDelegate.cpp",
|
||||
]
|
||||
configs = [
|
||||
"//build/userland:userland_flags",
|
||||
]
|
||||
deplibs = [
|
||||
"libcxx",
|
||||
"libfoundation",
|
||||
"libg",
|
||||
"libui"
|
||||
]
|
||||
}
|
||||
179
userland/applications/calculator/ViewController.h
Normal file
179
userland/applications/calculator/ViewController.h
Normal file
@@ -0,0 +1,179 @@
|
||||
#pragma once
|
||||
#include <libui/App.h>
|
||||
#include <libui/Button.h>
|
||||
#include <libui/Label.h>
|
||||
#include <libui/StackView.h>
|
||||
#include <libui/View.h>
|
||||
#include <libui/ViewController.h>
|
||||
#include <libui/Window.h>
|
||||
#include <memory>
|
||||
#include <sys/types.h>
|
||||
#include <sys/utsname.h>
|
||||
|
||||
// TODO: Add enum for colors.
|
||||
|
||||
class ViewController : public UI::ViewController<UI::View> {
|
||||
public:
|
||||
ViewController(UI::View& view)
|
||||
: UI::ViewController<UI::View>(view)
|
||||
{
|
||||
}
|
||||
virtual ~ViewController() = default;
|
||||
|
||||
static constexpr const char* button_titles[20] = {
|
||||
"c",
|
||||
"+/-",
|
||||
"%",
|
||||
"/",
|
||||
"7",
|
||||
"8",
|
||||
"9",
|
||||
"*",
|
||||
"4",
|
||||
"5",
|
||||
"6",
|
||||
"-",
|
||||
"1",
|
||||
"2",
|
||||
"3",
|
||||
"+",
|
||||
"0",
|
||||
"00",
|
||||
",",
|
||||
"=",
|
||||
};
|
||||
|
||||
constexpr size_t button_size() { return 48; }
|
||||
constexpr size_t spacing_size() { return 8; }
|
||||
|
||||
bool is_operation(char a) { return a == '*' || a == '/' || a == '+' || a == '-'; }
|
||||
|
||||
void on_button_click(UI::Button* sender)
|
||||
{
|
||||
// Check if it is a number
|
||||
if (sender->title().size() == 1 && '0' <= sender->title()[0] && sender->title()[0] <= '9') {
|
||||
m_current_number.push_back(sender->title()[0]);
|
||||
m_answer_label_ptr->set_text(m_current_number);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if it is an operation
|
||||
if (sender->title().size() == 1 && is_operation(sender->title()[0])) {
|
||||
var_number1 = std::atoi(m_current_number.c_str());
|
||||
m_current_number.clear();
|
||||
m_operation = sender->title()[0];
|
||||
m_active_operation_button = sender;
|
||||
sender->set_background_color(LG::Color(165, 201, 242));
|
||||
return;
|
||||
}
|
||||
|
||||
if (sender->title().size() == 1 && sender->title()[0] == '=') {
|
||||
int var_number2 = std::atoi(m_current_number.c_str());
|
||||
switch (m_operation) {
|
||||
case '+':
|
||||
var_number1 = var_number1 + var_number2;
|
||||
m_current_number = std::to_string(var_number1);
|
||||
m_answer_label_ptr->set_text(m_current_number);
|
||||
m_active_operation_button->set_background_color(LG::Color(231, 240, 250));
|
||||
break;
|
||||
case '-':
|
||||
var_number1 = var_number1 - var_number2;
|
||||
m_current_number = std::to_string(var_number1);
|
||||
m_answer_label_ptr->set_text(m_current_number);
|
||||
m_active_operation_button->set_background_color(LG::Color(231, 240, 250));
|
||||
break;
|
||||
case '*':
|
||||
var_number1 = var_number1 * var_number2;
|
||||
m_current_number = std::to_string(var_number1);
|
||||
m_answer_label_ptr->set_text(m_current_number);
|
||||
m_active_operation_button->set_background_color(LG::Color(231, 240, 250));
|
||||
break;
|
||||
case '/':
|
||||
m_active_operation_button->set_background_color(LG::Color(231, 240, 250));
|
||||
if (var_number2 == 0) {
|
||||
m_current_number = "";
|
||||
m_answer_label_ptr->set_text("Error");
|
||||
return;
|
||||
}
|
||||
var_number1 = var_number1 / var_number2;
|
||||
m_current_number = std::to_string(var_number1);
|
||||
m_answer_label_ptr->set_text(m_current_number);
|
||||
break;
|
||||
default:
|
||||
m_current_number = "";
|
||||
m_answer_label_ptr->set_text("Error");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (sender->title().size() == 1 && sender->title()[0] == 'c') {
|
||||
m_operation = ' ';
|
||||
m_active_operation_button = nullptr;
|
||||
m_answer_label_ptr->set_text("0");
|
||||
m_current_number.clear();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void view_did_load() override
|
||||
{
|
||||
view().set_background_color(LG::Color::LightSystemBackground);
|
||||
|
||||
auto& main_stackview = view().add_subview<UI::StackView>(LG::Rect(0, 0, 0, button_size() * 5 + spacing_size() * 4));
|
||||
main_stackview.set_background_color(LG::Color::LightSystemBackground);
|
||||
main_stackview.set_axis(UI::LayoutConstraints::Axis::Vertical);
|
||||
main_stackview.set_spacing(spacing_size());
|
||||
view().add_constraint(UI::Constraint(main_stackview, UI::Constraint::Attribute::Left, UI::Constraint::Relation::Equal, UI::SafeArea::Left));
|
||||
view().add_constraint(UI::Constraint(main_stackview, UI::Constraint::Attribute::Right, UI::Constraint::Relation::Equal, UI::SafeArea::Right));
|
||||
view().add_constraint(UI::Constraint(main_stackview, UI::Constraint::Attribute::Bottom, UI::Constraint::Relation::Equal, UI::SafeArea::Bottom));
|
||||
|
||||
for (int i = 0; i < 5; i++) {
|
||||
auto& stackview = main_stackview.add_arranged_subview<UI::StackView>();
|
||||
stackview.set_background_color(LG::Color::LightSystemBackground);
|
||||
stackview.set_distribution(UI::StackView::Distribution::EqualSpacing);
|
||||
main_stackview.add_constraint(UI::Constraint(stackview, UI::Constraint::Attribute::Left, UI::Constraint::Relation::Equal, main_stackview, UI::Constraint::Attribute::Left, 1, 0));
|
||||
main_stackview.add_constraint(UI::Constraint(stackview, UI::Constraint::Attribute::Right, UI::Constraint::Relation::Equal, main_stackview, UI::Constraint::Attribute::Right, 1, 0));
|
||||
main_stackview.add_constraint(UI::Constraint(stackview, UI::Constraint::Attribute::Height, UI::Constraint::Relation::Equal, button_size()));
|
||||
|
||||
for (int j = 0; j < 4; j++) {
|
||||
auto& view1 = stackview.add_arranged_subview<UI::Button>();
|
||||
if (j == 3 && i == 4) {
|
||||
view1.set_background_color(LG::Color(198, 166, 242));
|
||||
view1.set_title_color(LG::Color::White);
|
||||
} else if (j == 3 || i == 0) {
|
||||
view1.set_background_color(LG::Color(231, 240, 250));
|
||||
view1.set_title_color(LG::Color(35, 70, 106));
|
||||
} else {
|
||||
view1.set_background_color(LG::Color::LightSystemButton);
|
||||
view1.set_title_color(LG::Color::DarkSystemText);
|
||||
}
|
||||
view1.set_font(LG::Font::system_bold_font(14));
|
||||
view1.set_title(button_titles[i * 4 + j]);
|
||||
view1.set_alignment(UI::Text::Alignment::Center);
|
||||
view1.add_target([this](UI::View* sender) { this->on_button_click(static_cast<UI::Button*>(sender)); }, UI::Event::Type::MouseUpEvent);
|
||||
view1.layer().set_corner_mask(LG::CornerMask(button_size() / 2));
|
||||
stackview.add_constraint(UI::Constraint(view1, UI::Constraint::Attribute::Height, UI::Constraint::Relation::Equal, button_size()));
|
||||
stackview.add_constraint(UI::Constraint(view1, UI::Constraint::Attribute::Width, UI::Constraint::Relation::Equal, button_size()));
|
||||
}
|
||||
}
|
||||
|
||||
auto& answer_label = view().add_subview<UI::Label>(LG::Rect(0, 0, 0, 40));
|
||||
answer_label.set_text("0");
|
||||
answer_label.set_text_color(LG::Color::DarkSystemText);
|
||||
answer_label.set_alignment(UI::Text::Alignment::Right);
|
||||
answer_label.set_font(LG::Font::system_font(36));
|
||||
m_answer_label_ptr = &answer_label;
|
||||
view().add_constraint(UI::Constraint(answer_label, UI::Constraint::Attribute::Left, UI::Constraint::Relation::Equal, UI::SafeArea::Left));
|
||||
view().add_constraint(UI::Constraint(answer_label, UI::Constraint::Attribute::Right, UI::Constraint::Relation::Equal, UI::SafeArea::Right));
|
||||
view().add_constraint(UI::Constraint(answer_label, UI::Constraint::Attribute::Bottom, UI::Constraint::Relation::Equal, main_stackview, UI::Constraint::Attribute::Top, 1, -8));
|
||||
|
||||
view().set_needs_layout();
|
||||
}
|
||||
|
||||
private:
|
||||
UI::Label* m_answer_label_ptr { nullptr };
|
||||
std::string m_current_number;
|
||||
int var_number1 { 0 };
|
||||
char m_operation;
|
||||
UI::Button* m_active_operation_button { nullptr };
|
||||
};
|
||||
5
userland/applications/terminal/.info.mk
Normal file
5
userland/applications/terminal/.info.mk
Normal file
@@ -0,0 +1,5 @@
|
||||
APPS += TERMINAL
|
||||
|
||||
TERMINAL_NAME = terminal
|
||||
TERMINAL_LIBS = cxx ui
|
||||
TERMINAL_INSTALL_PATH = bin/
|
||||
62
userland/applications/terminal/AppDelegate.cpp
Normal file
62
userland/applications/terminal/AppDelegate.cpp
Normal file
@@ -0,0 +1,62 @@
|
||||
#include "TerminalView.h"
|
||||
#include "TerminalViewController.h"
|
||||
#include <csignal>
|
||||
#include <libui/AppDelegate.h>
|
||||
|
||||
static int shell_pid = 0;
|
||||
|
||||
int setup_shell()
|
||||
{
|
||||
int ptmx = posix_openpt(O_RDONLY);
|
||||
if (ptmx < 0) {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
int f = fork();
|
||||
if (f == 0) {
|
||||
char* pname = ptsname(ptmx);
|
||||
if (!pname) {
|
||||
return -1;
|
||||
}
|
||||
close(0);
|
||||
close(1);
|
||||
close(2);
|
||||
open(pname, O_RDONLY);
|
||||
open(pname, O_WRONLY);
|
||||
open(pname, O_WRONLY);
|
||||
execlp("/bin/tinysh", "/bin/tinysh", NULL);
|
||||
std::abort();
|
||||
}
|
||||
|
||||
shell_pid = f;
|
||||
return ptmx;
|
||||
}
|
||||
|
||||
class AppDelegate : public UI::AppDelegate {
|
||||
public:
|
||||
AppDelegate() = default;
|
||||
virtual ~AppDelegate() = default;
|
||||
|
||||
LG::Size preferred_desktop_window_size() const override { return LG::Size(400, 300); }
|
||||
const char* icon_path() const override { return "/res/icons/apps/terminal.icon"; }
|
||||
|
||||
bool application() override
|
||||
{
|
||||
int ptmx = setup_shell();
|
||||
auto style = StatusBarStyle(LG::Color(58, 58, 64)).set_light_text();
|
||||
auto& window = std::xos::construct<UI::Window>("Terminal", window_size(), icon_path(), style);
|
||||
|
||||
auto& superview = window.create_superview<TerminalView, TerminalViewController>(ptmx);
|
||||
window.set_focused_view(superview);
|
||||
return true;
|
||||
}
|
||||
|
||||
void application_will_terminate() override
|
||||
{
|
||||
std::kill(shell_pid, 9);
|
||||
}
|
||||
|
||||
private:
|
||||
};
|
||||
|
||||
SET_APP_DELEGATE(AppDelegate);
|
||||
16
userland/applications/terminal/BUILD.gn
Normal file
16
userland/applications/terminal/BUILD.gn
Normal file
@@ -0,0 +1,16 @@
|
||||
import("//build/userland/TEMPLATE.gni")
|
||||
|
||||
xOS_application("terminal") {
|
||||
display_name = "Terminal"
|
||||
sources = [
|
||||
"AppDelegate.cpp",
|
||||
"TerminalView.cpp",
|
||||
]
|
||||
configs = [ "//build/userland:userland_flags" ]
|
||||
deplibs = [
|
||||
"libcxx",
|
||||
"libfoundation",
|
||||
"libg",
|
||||
"libui",
|
||||
]
|
||||
}
|
||||
229
userland/applications/terminal/TerminalView.cpp
Normal file
229
userland/applications/terminal/TerminalView.cpp
Normal file
@@ -0,0 +1,229 @@
|
||||
#include "TerminalView.h"
|
||||
#include <algorithm>
|
||||
#include <libfoundation/EventLoop.h>
|
||||
#include <libfoundation/KeyboardMapping.h>
|
||||
#include <libg/Color.h>
|
||||
#include <libui/Context.h>
|
||||
|
||||
TerminalView::TerminalView(UI::View* superview, const LG::Rect& frame, int ptmx)
|
||||
: UI::View(superview, frame)
|
||||
, m_ptmx(ptmx)
|
||||
{
|
||||
recalc_dimensions(frame);
|
||||
}
|
||||
|
||||
TerminalView::TerminalView(UI::View* superview, UI::Window* window, const LG::Rect& frame, int ptmx)
|
||||
: UI::View(superview, window, frame)
|
||||
, m_ptmx(ptmx)
|
||||
{
|
||||
recalc_dimensions(frame);
|
||||
LFoundation::EventLoop::the().add(LFoundation::Timer([this] {
|
||||
this->m_cursor_visible = !this->m_cursor_visible;
|
||||
this->invalidate_cursor_glyph();
|
||||
},
|
||||
400, LFoundation::Timer::Repeat));
|
||||
}
|
||||
|
||||
void TerminalView::recalc_dimensions(const LG::Rect& frame)
|
||||
{
|
||||
m_max_rows = (frame.height() - padding() - UI::SafeArea::Bottom) / glyph_height();
|
||||
m_max_cols = (frame.width() - 2 * padding()) / glyph_width();
|
||||
// FIXME: Add copy and resize on window resize.
|
||||
char* new_data = (char*)malloc(m_max_rows * m_max_cols);
|
||||
memset(new_data, 0, m_max_rows * m_max_cols);
|
||||
if (m_display_data) {
|
||||
free(m_display_data);
|
||||
}
|
||||
m_display_data = new_data;
|
||||
}
|
||||
|
||||
void TerminalView::display(const LG::Rect& rect)
|
||||
{
|
||||
LG::Context ctx = UI::graphics_current_context();
|
||||
ctx.add_clip(rect);
|
||||
|
||||
ctx.set_fill_color(background_color());
|
||||
ctx.fill(bounds());
|
||||
|
||||
ctx.set_fill_color(cursor_color());
|
||||
auto cursor_left_corner = pos_on_screen();
|
||||
ctx.fill(LG::Rect(cursor_left_corner.x(), cursor_left_corner.y(), cursor_width(), glyph_height()));
|
||||
|
||||
auto& f = font();
|
||||
ctx.set_fill_color(font_color());
|
||||
LG::Point<int> text_start { padding(), padding() };
|
||||
|
||||
for (int i = 0; i < m_max_rows; i++) {
|
||||
for (int j = 0; j < m_max_cols; j++) {
|
||||
int idx = i * m_max_cols + j;
|
||||
ctx.draw(text_start, f.glyph(m_display_data[idx]));
|
||||
text_start.offset_by(glyph_width(), 0);
|
||||
}
|
||||
text_start.set_x(padding());
|
||||
text_start.offset_by(0, glyph_height());
|
||||
}
|
||||
}
|
||||
|
||||
void TerminalView::scroll_line()
|
||||
{
|
||||
data_do_new_line();
|
||||
set_needs_display();
|
||||
}
|
||||
|
||||
void TerminalView::data_do_new_line()
|
||||
{
|
||||
char* data_plus_line = m_display_data + (m_max_cols);
|
||||
char* data_end_minus_line = m_display_data + (m_max_rows - 1) * m_max_cols;
|
||||
memmove(m_display_data, data_plus_line, (m_max_rows - 1) * m_max_cols);
|
||||
memset(data_end_minus_line, 0, m_max_cols);
|
||||
}
|
||||
|
||||
WindowStatus TerminalView::cursor_positions_do_new_line()
|
||||
{
|
||||
m_col = 0;
|
||||
m_row++;
|
||||
if (m_row == m_max_rows) {
|
||||
m_row--;
|
||||
return DoNewLine;
|
||||
}
|
||||
return Normal;
|
||||
}
|
||||
|
||||
WindowStatus TerminalView::cursor_position_move_right()
|
||||
{
|
||||
m_col++;
|
||||
if (m_col == m_max_cols) {
|
||||
return cursor_positions_do_new_line();
|
||||
}
|
||||
return Normal;
|
||||
}
|
||||
|
||||
WindowStatus TerminalView::cursor_position_move_left()
|
||||
{
|
||||
m_col--;
|
||||
if (m_col == 0 && m_row > 0) {
|
||||
m_row--;
|
||||
}
|
||||
return Normal;
|
||||
}
|
||||
|
||||
void TerminalView::new_line()
|
||||
{
|
||||
will_move_cursor();
|
||||
WindowStatus status = cursor_positions_do_new_line();
|
||||
switch (status) {
|
||||
case DoNewLine:
|
||||
scroll_line();
|
||||
break;
|
||||
case Normal:
|
||||
break;
|
||||
}
|
||||
did_move_cursor();
|
||||
}
|
||||
|
||||
void TerminalView::increment_counter()
|
||||
{
|
||||
will_move_cursor();
|
||||
m_col++;
|
||||
if (m_col == m_max_cols) {
|
||||
new_line();
|
||||
}
|
||||
did_move_cursor();
|
||||
}
|
||||
|
||||
void TerminalView::decrement_counter()
|
||||
{
|
||||
will_move_cursor();
|
||||
m_col--;
|
||||
if (m_col == 0) {
|
||||
m_row--;
|
||||
}
|
||||
did_move_cursor();
|
||||
}
|
||||
|
||||
void TerminalView::put_char(char c)
|
||||
{
|
||||
auto pt = pos_on_screen();
|
||||
set_needs_display(LG::Rect(pt.x(), pt.y(), glyph_width(), glyph_height()));
|
||||
m_display_data[pos_in_data()] = c;
|
||||
}
|
||||
|
||||
void TerminalView::push_back_char(char c)
|
||||
{
|
||||
if (c == '\n') {
|
||||
new_line();
|
||||
return;
|
||||
}
|
||||
put_char(c);
|
||||
increment_counter();
|
||||
}
|
||||
|
||||
void TerminalView::put_text(const std::string& data)
|
||||
{
|
||||
auto current_pos = pos_on_screen();
|
||||
LG::Point<int> top_left_update_location { current_pos.x(), current_pos.y() };
|
||||
LG::Point<int> bottom_right_update_location { current_pos.x(), current_pos.y() };
|
||||
auto set_to_redraw_full_screen = [&]() {
|
||||
data_do_new_line();
|
||||
top_left_update_location = { bounds().min_x(), bounds().min_y() };
|
||||
bottom_right_update_location = { bounds().max_x(), bounds().max_y() };
|
||||
};
|
||||
|
||||
will_move_cursor();
|
||||
int n = data.size();
|
||||
for (int i = 0; i < n; i++) {
|
||||
char c = data[i];
|
||||
if (c == '\n') {
|
||||
auto status = cursor_positions_do_new_line();
|
||||
if (status == DoNewLine) {
|
||||
set_to_redraw_full_screen();
|
||||
}
|
||||
} else {
|
||||
auto pt = pos_on_screen();
|
||||
data_set_char(c);
|
||||
auto status = cursor_position_move_right();
|
||||
if (status == DoNewLine) {
|
||||
set_to_redraw_full_screen();
|
||||
} else {
|
||||
top_left_update_location.set_x(std::min(top_left_update_location.x(), pt.x()));
|
||||
top_left_update_location.set_y(std::min(top_left_update_location.y(), pt.y()));
|
||||
bottom_right_update_location.set_x(std::max(bottom_right_update_location.x(), pt.x() + glyph_width()));
|
||||
bottom_right_update_location.set_y(std::max(bottom_right_update_location.y(), pt.y() + glyph_height()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto w = bottom_right_update_location.x() - top_left_update_location.x() + 1;
|
||||
auto h = bottom_right_update_location.y() - top_left_update_location.y() + 1;
|
||||
set_needs_display(LG::Rect(top_left_update_location.x(), top_left_update_location.y(), w, h));
|
||||
did_move_cursor();
|
||||
}
|
||||
|
||||
void TerminalView::send_input()
|
||||
{
|
||||
write(ptmx(), m_input.c_str(), m_input.size());
|
||||
m_input.clear();
|
||||
}
|
||||
|
||||
void TerminalView::receive_keyup_event(UI::KeyUpEvent&)
|
||||
{
|
||||
}
|
||||
|
||||
void TerminalView::receive_keydown_event(UI::KeyDownEvent& event)
|
||||
{
|
||||
// FIXME: More symbols and static size of font
|
||||
if (event.key() == LFoundation::Keycode::KEY_BACKSPACE) {
|
||||
if (m_input.size()) {
|
||||
m_input.pop_back();
|
||||
decrement_counter();
|
||||
put_char(' ');
|
||||
}
|
||||
} else if (event.key() == LFoundation::Keycode::KEY_RETURN) {
|
||||
m_input.push_back('\n');
|
||||
push_back_char('\n');
|
||||
send_input();
|
||||
} else if (event.key() < 128) {
|
||||
m_input.push_back(char(event.key()));
|
||||
push_back_char(char(event.key()));
|
||||
}
|
||||
}
|
||||
84
userland/applications/terminal/TerminalView.h
Normal file
84
userland/applications/terminal/TerminalView.h
Normal file
@@ -0,0 +1,84 @@
|
||||
#pragma once
|
||||
#include <libg/Font.h>
|
||||
#include <libui/View.h>
|
||||
#include <string>
|
||||
|
||||
enum WindowStatus {
|
||||
Normal,
|
||||
DoNewLine,
|
||||
};
|
||||
|
||||
class TerminalView : public UI::View {
|
||||
UI_OBJECT();
|
||||
|
||||
public:
|
||||
TerminalView(UI::View* superview, const LG::Rect&, int ptmx);
|
||||
TerminalView(UI::View* superview, UI::Window* window, const LG::Rect&, int ptmx);
|
||||
|
||||
const LG::Color& font_color() const { return m_font_color; }
|
||||
const LG::Color cursor_color() const { return m_cursor_visible ? LG::Color(80, 80, 80, 255) : background_color(); }
|
||||
const LG::Color& background_color() const { return m_background_color; }
|
||||
inline const LG::Font& font() const { return *m_font_ptr; }
|
||||
|
||||
inline int glyph_width() const { return font().glyph('.').width(); }
|
||||
inline int glyph_height() const { return font().size() + 2; }
|
||||
|
||||
inline LG::Point<int> pos_on_screen() const { return { (int)m_col * glyph_width() + padding(), (int)m_row * glyph_height() + padding() }; }
|
||||
inline int pos_in_data() const { return m_max_cols * m_row + m_col; }
|
||||
|
||||
void put_char(char c);
|
||||
void put_text(const std::string& data);
|
||||
|
||||
void display(const LG::Rect& rect) override;
|
||||
void receive_keyup_event(UI::KeyUpEvent&) override;
|
||||
void receive_keydown_event(UI::KeyDownEvent&) override;
|
||||
|
||||
int ptmx() const { return m_ptmx; }
|
||||
|
||||
private:
|
||||
void terminal_init();
|
||||
|
||||
WindowStatus cursor_positions_do_new_line();
|
||||
WindowStatus cursor_position_move_right();
|
||||
WindowStatus cursor_position_move_left();
|
||||
void data_do_new_line();
|
||||
inline void data_set_char(char c)
|
||||
{
|
||||
m_display_data[pos_in_data()] = c;
|
||||
}
|
||||
|
||||
void scroll_line();
|
||||
void new_line();
|
||||
void increment_counter();
|
||||
void decrement_counter();
|
||||
|
||||
void recalc_dimensions(const LG::Rect&);
|
||||
void push_back_char(char c);
|
||||
void send_input();
|
||||
|
||||
inline void invalidate_cursor_glyph()
|
||||
{
|
||||
auto pt = pos_on_screen();
|
||||
set_needs_display(LG::Rect(pt.x(), pt.y(), cursor_width() + spacing(), glyph_height()));
|
||||
}
|
||||
inline void will_move_cursor() { invalidate_cursor_glyph(); }
|
||||
inline void did_move_cursor() { invalidate_cursor_glyph(); }
|
||||
|
||||
constexpr int padding() const { return 2; }
|
||||
constexpr int spacing() const { return 2; }
|
||||
constexpr int cursor_width() const { return 5; }
|
||||
|
||||
int m_ptmx { -1 };
|
||||
std::string m_input {};
|
||||
|
||||
bool m_cursor_visible { true };
|
||||
LG::Color m_background_color { LG::Color(47, 47, 53) };
|
||||
LG::Color m_font_color { LG::Color::LightSystemText };
|
||||
LG::Font* m_font_ptr { LG::Font::load_from_file("/res/fonts/Liza.font/10/regular.font") };
|
||||
|
||||
size_t m_max_cols { 0 };
|
||||
size_t m_max_rows { 0 };
|
||||
size_t m_col { 0 };
|
||||
size_t m_row { 0 };
|
||||
char* m_display_data { nullptr };
|
||||
};
|
||||
34
userland/applications/terminal/TerminalViewController.h
Normal file
34
userland/applications/terminal/TerminalViewController.h
Normal file
@@ -0,0 +1,34 @@
|
||||
#pragma once
|
||||
#include "TerminalView.h"
|
||||
#include <libui/ViewController.h>
|
||||
#include <memory>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
class TerminalViewController : public UI::ViewController<TerminalView> {
|
||||
public:
|
||||
TerminalViewController(TerminalView& view)
|
||||
: UI::ViewController<TerminalView>(view)
|
||||
{
|
||||
}
|
||||
virtual ~TerminalViewController() = default;
|
||||
|
||||
void init_listners()
|
||||
{
|
||||
LFoundation::EventLoop::the().add(
|
||||
view().ptmx(), [this] {
|
||||
char text[256];
|
||||
int cnt = read(view().ptmx(), text, 255);
|
||||
text[cnt] = '\0';
|
||||
view().put_text(std::string(text, cnt));
|
||||
},
|
||||
nullptr);
|
||||
}
|
||||
|
||||
void view_did_load() override
|
||||
{
|
||||
init_listners();
|
||||
}
|
||||
|
||||
private:
|
||||
};
|
||||
15
userland/servers/launch_server/BUILD.gn
Normal file
15
userland/servers/launch_server/BUILD.gn
Normal file
@@ -0,0 +1,15 @@
|
||||
import("//build/userland/TEMPLATE.gni")
|
||||
|
||||
xOS_executable("launch_server") {
|
||||
signexec = true
|
||||
install_path = "System/"
|
||||
sources = [
|
||||
"LaunchWatchdog.cpp",
|
||||
"main.cpp",
|
||||
]
|
||||
configs = [ "//build/userland:userland_flags" ]
|
||||
deplibs = [
|
||||
"libcxx",
|
||||
"libfoundation",
|
||||
]
|
||||
}
|
||||
41
userland/servers/launch_server/Exec.h
Normal file
41
userland/servers/launch_server/Exec.h
Normal file
@@ -0,0 +1,41 @@
|
||||
#pragma once
|
||||
|
||||
#include <csignal>
|
||||
#include <string>
|
||||
#include <unistd.h>
|
||||
#include <vector>
|
||||
|
||||
namespace LaunchServer {
|
||||
|
||||
class Exec {
|
||||
public:
|
||||
enum Flags {
|
||||
None = 0,
|
||||
RestartOnFail = (1 << 0),
|
||||
};
|
||||
|
||||
Exec(const std::string& path, Flags flag = Flags::None)
|
||||
: m_path(path)
|
||||
, m_flags(flag)
|
||||
{
|
||||
}
|
||||
~Exec() = default;
|
||||
|
||||
void launch()
|
||||
{
|
||||
m_pid = fork();
|
||||
if (m_pid == 0) {
|
||||
execlp(m_path.c_str(), m_path.c_str(), NULL);
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
|
||||
bool is_alive() const { return m_pid > 0 && kill(m_pid, 0) == 0; }
|
||||
|
||||
private:
|
||||
std::string m_path;
|
||||
Flags m_flags;
|
||||
int m_pid { -1 };
|
||||
};
|
||||
|
||||
}
|
||||
13
userland/servers/launch_server/LaunchWatchdog.cpp
Normal file
13
userland/servers/launch_server/LaunchWatchdog.cpp
Normal file
@@ -0,0 +1,13 @@
|
||||
#include "LaunchWatchdog.h"
|
||||
|
||||
namespace LaunchServer {
|
||||
|
||||
LaunchWatchdog* s_LaunchServer_LaunchWatchdog_the = nullptr;
|
||||
|
||||
LaunchWatchdog::LaunchWatchdog()
|
||||
: LFoundation::EventReceiver()
|
||||
{
|
||||
s_LaunchServer_LaunchWatchdog_the = this;
|
||||
}
|
||||
|
||||
}
|
||||
37
userland/servers/launch_server/LaunchWatchdog.h
Normal file
37
userland/servers/launch_server/LaunchWatchdog.h
Normal file
@@ -0,0 +1,37 @@
|
||||
#pragma once
|
||||
|
||||
#include "Exec.h"
|
||||
#include <iostream>
|
||||
#include <libfoundation/EventReceiver.h>
|
||||
#include <vector>
|
||||
|
||||
namespace LaunchServer {
|
||||
|
||||
class LaunchWatchdog : public LFoundation::EventReceiver {
|
||||
public:
|
||||
inline static LaunchWatchdog& the()
|
||||
{
|
||||
extern LaunchWatchdog* s_LaunchServer_LaunchWatchdog_the;
|
||||
return *s_LaunchServer_LaunchWatchdog_the;
|
||||
}
|
||||
|
||||
LaunchWatchdog();
|
||||
~LaunchWatchdog() = default;
|
||||
|
||||
void tick()
|
||||
{
|
||||
for (auto& exec : m_execs) {
|
||||
if (!exec.is_alive()) {
|
||||
exec.launch();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void add(const Exec& exec) { m_execs.push_back(exec); }
|
||||
void add(Exec&& exec) { m_execs.push_back(std::move(exec)); }
|
||||
|
||||
private:
|
||||
std::vector<Exec> m_execs;
|
||||
};
|
||||
|
||||
}
|
||||
34
userland/servers/launch_server/main.cpp
Normal file
34
userland/servers/launch_server/main.cpp
Normal file
@@ -0,0 +1,34 @@
|
||||
#include "LaunchWatchdog.h"
|
||||
#include <libfoundation/EventLoop.h>
|
||||
#include <libfoundation/json/Parser.h>
|
||||
#include <new>
|
||||
|
||||
void load_from_file(LaunchServer::LaunchWatchdog& launch_watchdog)
|
||||
{
|
||||
auto json_parser = LFoundation::Json::Parser("/System/launch_server_config.json");
|
||||
LFoundation::Json::Object* jobj_root = json_parser.object();
|
||||
if (jobj_root->invalid()) {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
auto* jdict_root = jobj_root->cast_to<LFoundation::Json::DictObject>();
|
||||
auto* jlaunch_list = jdict_root->data()["launch"]->cast_to<LFoundation::Json::ListObject>();
|
||||
for (auto* jobj : jlaunch_list->data()) {
|
||||
const std::string& strdata = jobj->cast_to<LFoundation::Json::StringObject>()->data();
|
||||
launch_watchdog.add(LaunchServer::Exec(strdata, LaunchServer::Exec::Flags::RestartOnFail));
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
auto* event_loop = new LFoundation::EventLoop();
|
||||
auto* launch_watchdog = new LaunchServer::LaunchWatchdog();
|
||||
|
||||
load_from_file(*launch_watchdog);
|
||||
launch_watchdog->tick();
|
||||
event_loop->add(LFoundation::Timer([launch_watchdog] {
|
||||
launch_watchdog->tick();
|
||||
},
|
||||
5000, LFoundation::Timer::Repeat));
|
||||
return event_loop->run();
|
||||
}
|
||||
53
userland/servers/window_server/BUILD.gn
Normal file
53
userland/servers/window_server/BUILD.gn
Normal file
@@ -0,0 +1,53 @@
|
||||
import("//build/userland/TEMPLATE.gni")
|
||||
|
||||
xOS_executable("window_server") {
|
||||
signexec = true
|
||||
install_path = "System/"
|
||||
sources = [
|
||||
"src/Components/Base/BaseWindow.cpp",
|
||||
"src/Components/Elements/Button.cpp",
|
||||
"src/Components/LoadingScreen/LoadingScreen.cpp",
|
||||
"src/Components/MenuBar/MenuBar.cpp",
|
||||
"src/Components/Popup/Popup.cpp",
|
||||
"src/Devices/Devices.cpp",
|
||||
"src/Devices/Screen.cpp",
|
||||
"src/IPC/Connection.cpp",
|
||||
"src/IPC/ServerDecoder.cpp",
|
||||
"src/Managers/Compositor.cpp",
|
||||
"src/Managers/CursorManager.cpp",
|
||||
"src/Managers/ResourceManager.cpp",
|
||||
"src/Managers/WindowManager.cpp",
|
||||
"src/main.cpp",
|
||||
]
|
||||
|
||||
if (device_type == "desktop") {
|
||||
cflags = [ "-DTARGET_DESKTOP" ]
|
||||
sources += [
|
||||
"src/Target/Desktop/Window.cpp",
|
||||
"src/Target/Desktop/WindowFrame.cpp",
|
||||
]
|
||||
}
|
||||
if (device_type == "mobile") {
|
||||
cflags = [ "-DTARGET_MOBILE" ]
|
||||
sources += [
|
||||
"src/Components/ControlBar/ControlBar.cpp",
|
||||
"src/Target/Mobile/Window.cpp",
|
||||
]
|
||||
}
|
||||
|
||||
configs = [ "//build/userland:userland_flags" ]
|
||||
|
||||
if (host == "llvm") {
|
||||
cflags += [ "-flto" ]
|
||||
}
|
||||
|
||||
deplibs = [
|
||||
"libcxx",
|
||||
"libfoundation",
|
||||
"libipc",
|
||||
"libg",
|
||||
"libapi",
|
||||
]
|
||||
|
||||
include_dirs = [ "//servers/window_server/src/" ]
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
#include "BaseWindow.h"
|
||||
#include "../../Managers/WindowManager.h"
|
||||
#include <utility>
|
||||
|
||||
namespace WinServer {
|
||||
|
||||
BaseWindow::BaseWindow(int connection_id, int id, CreateWindowMessage& msg)
|
||||
: m_id(id)
|
||||
, m_connection_id(connection_id)
|
||||
, m_type((WindowType)msg.type())
|
||||
, m_buffer(msg.buffer_id())
|
||||
, m_content_bitmap()
|
||||
, m_bounds(0, 0, 0, 0)
|
||||
, m_content_bounds(0, 0, 0, 0)
|
||||
, m_app_name(msg.title().string())
|
||||
, m_bundle_id(msg.bundle_id().string())
|
||||
{
|
||||
}
|
||||
|
||||
BaseWindow::BaseWindow(BaseWindow&& win)
|
||||
: m_id(win.m_id)
|
||||
, m_connection_id(win.m_connection_id)
|
||||
, m_buffer(win.m_buffer)
|
||||
, m_content_bitmap(std::move(win.m_content_bitmap))
|
||||
, m_bounds(win.m_bounds)
|
||||
, m_content_bounds(win.m_content_bounds)
|
||||
, m_app_name(std::move(win.m_app_name))
|
||||
, m_icon_path(std::move(win.m_icon_path))
|
||||
, m_bundle_id(std::move(win.m_bundle_id))
|
||||
{
|
||||
}
|
||||
|
||||
void BaseWindow::set_buffer(int buffer_id, LG::Size sz, LG::PixelBitmapFormat fmt)
|
||||
{
|
||||
m_buffer.open(buffer_id);
|
||||
m_content_bitmap = LG::PixelBitmap(m_buffer.data(), sz.width(), sz.height());
|
||||
m_content_bitmap.set_format(fmt);
|
||||
}
|
||||
|
||||
} // namespace WinServer
|
||||
@@ -0,0 +1,92 @@
|
||||
#pragma once
|
||||
#include "../../IPC/Connection.h"
|
||||
#include "../MenuBar/MenuItem.h"
|
||||
#include <libapi/window_server/MessageContent/MenuBar.h>
|
||||
#include <libapi/window_server/MessageContent/Window.h>
|
||||
#include <libfoundation/SharedBuffer.h>
|
||||
#include <libg/PixelBitmap.h>
|
||||
#include <libg/Rect.h>
|
||||
#include <libg/Size.h>
|
||||
#include <sys/types.h>
|
||||
#include <utility>
|
||||
|
||||
namespace WinServer {
|
||||
|
||||
typedef uint32_t WindowEventMask;
|
||||
enum WindowEvent {
|
||||
WindowStatus = (1 << 0),
|
||||
WindowCreation = (1 << 1),
|
||||
IconChange = (1 << 2),
|
||||
TitleChange = (1 << 3),
|
||||
};
|
||||
|
||||
class BaseWindow {
|
||||
public:
|
||||
BaseWindow(int connection_id, int id, CreateWindowMessage& msg);
|
||||
BaseWindow(BaseWindow&& win);
|
||||
~BaseWindow() = default;
|
||||
|
||||
void set_buffer(int buffer_id, LG::Size sz, LG::PixelBitmapFormat fmt);
|
||||
|
||||
inline int id() const { return m_id; }
|
||||
inline int connection_id() const { return m_connection_id; }
|
||||
inline WindowType type() const { return m_type; }
|
||||
inline WindowEventMask event_mask() const { return m_event_mask; }
|
||||
inline void set_event_mask(WindowEventMask mask) { m_event_mask = mask; }
|
||||
|
||||
inline LFoundation::SharedBuffer<LG::Color>& buffer() { return m_buffer; }
|
||||
inline LG::PixelBitmap& content_bitmap() { return m_content_bitmap; }
|
||||
inline const LG::PixelBitmap& content_bitmap() const { return m_content_bitmap; }
|
||||
|
||||
inline LG::Rect& content_bounds() { return m_content_bounds; }
|
||||
inline const LG::Rect& content_bounds() const { return m_content_bounds; }
|
||||
|
||||
inline LG::Rect& bounds() { return m_bounds; }
|
||||
inline const LG::Rect& bounds() const { return m_bounds; }
|
||||
|
||||
inline const std::string& app_name() const { return m_app_name; }
|
||||
inline const std::string& bundle_id() const { return m_bundle_id; }
|
||||
|
||||
inline const std::string& app_title() const { return m_app_title; }
|
||||
inline void set_app_title(const std::string& title) { m_app_title = title, did_app_title_change(); }
|
||||
virtual void did_app_title_change() { }
|
||||
|
||||
inline const std::string& icon_path() const { return m_icon_path; }
|
||||
inline void set_icon_path(std::string&& name) { m_icon_path = std::move(name), did_icon_path_change(); }
|
||||
inline void set_icon_path(const std::string& name) { m_icon_path = name, did_icon_path_change(); }
|
||||
virtual void did_icon_path_change() { }
|
||||
|
||||
inline bool visible() const { return m_visible; }
|
||||
inline void set_visible(bool vis) { m_visible = vis; }
|
||||
|
||||
inline void set_needs_display(const LG::Rect& rect) const
|
||||
{
|
||||
DisplayMessage msg(connection_id(), rect);
|
||||
Connection::the().send_async_message(msg);
|
||||
}
|
||||
|
||||
inline void offset_by(int x_offset, int y_offset)
|
||||
{
|
||||
bounds().offset_by(x_offset, y_offset);
|
||||
content_bounds().offset_by(x_offset, y_offset);
|
||||
}
|
||||
|
||||
virtual void did_size_change(const LG::Size& size) { }
|
||||
|
||||
protected:
|
||||
int m_id { -1 };
|
||||
int m_connection_id { -1 };
|
||||
bool m_visible { true };
|
||||
WindowType m_type { WindowType::Standard };
|
||||
WindowEventMask m_event_mask { 0 };
|
||||
LG::Rect m_bounds;
|
||||
LG::Rect m_content_bounds;
|
||||
LG::PixelBitmap m_content_bitmap;
|
||||
std::string m_app_name {};
|
||||
std::string m_app_title {};
|
||||
std::string m_icon_path {};
|
||||
std::string m_bundle_id {};
|
||||
LFoundation::SharedBuffer<LG::Color> m_buffer;
|
||||
};
|
||||
|
||||
} // namespace WinServer
|
||||
@@ -0,0 +1,21 @@
|
||||
#include "ControlBar.h"
|
||||
#include "../../Devices/Screen.h"
|
||||
#include "../../Managers/Compositor.h"
|
||||
#include <libg/ImageLoaders/PNGLoader.h>
|
||||
|
||||
namespace WinServer {
|
||||
|
||||
ControlBar* s_WinServer_ControlBar_the = nullptr;
|
||||
|
||||
ControlBar::ControlBar()
|
||||
: m_bounds(0, Screen::the().bounds().height() - height(), Screen::the().bounds().width(), height())
|
||||
{
|
||||
s_WinServer_ControlBar_the = this;
|
||||
LG::PNG::PNGLoader loader;
|
||||
m_menu_icon = loader.load_from_file("/res/system/mobile/control.png");
|
||||
int x = width() / 2 - m_menu_icon.width() / 2;
|
||||
int y = Screen::the().bounds().height() - height() / 2 - m_menu_icon.height() / 2;
|
||||
m_button_bounds = LG::Rect(x, y, m_menu_icon.width(), m_menu_icon.height());
|
||||
}
|
||||
|
||||
} // namespace WinServer
|
||||
@@ -0,0 +1,40 @@
|
||||
#pragma once
|
||||
#include <libfoundation/Logger.h>
|
||||
#include <libg/Context.h>
|
||||
#include <libg/PixelBitmap.h>
|
||||
#include <libg/Point.h>
|
||||
#include <vector>
|
||||
|
||||
namespace WinServer {
|
||||
|
||||
class ControlBar {
|
||||
public:
|
||||
inline static ControlBar& the()
|
||||
{
|
||||
extern ControlBar* s_WinServer_ControlBar_the;
|
||||
return *s_WinServer_ControlBar_the;
|
||||
}
|
||||
|
||||
ControlBar();
|
||||
|
||||
static constexpr size_t height() { return 20; }
|
||||
static constexpr size_t padding() { return 4; }
|
||||
static constexpr size_t menubar_content_offset() { return 20; }
|
||||
|
||||
size_t width() const { return m_bounds.width(); }
|
||||
LG::Rect& bounds() { return m_bounds; }
|
||||
const LG::Rect& bounds() const { return m_bounds; }
|
||||
const LG::Rect& control_button_bounds() const { return m_button_bounds; }
|
||||
|
||||
[[gnu::always_inline]] inline void draw(LG::Context& ctx)
|
||||
{
|
||||
ctx.draw({ control_button_bounds().min_x(), control_button_bounds().min_y() }, m_menu_icon);
|
||||
}
|
||||
|
||||
private:
|
||||
LG::Rect m_bounds;
|
||||
LG::Rect m_button_bounds;
|
||||
LG::PixelBitmap m_menu_icon;
|
||||
};
|
||||
|
||||
} // namespace WinServer
|
||||
@@ -0,0 +1,37 @@
|
||||
#include "Button.h"
|
||||
#include "../Helpers/TextDrawer.h"
|
||||
#include <algorithm>
|
||||
|
||||
namespace WinServer {
|
||||
|
||||
void Button::recalc_dims()
|
||||
{
|
||||
size_t new_width = 0;
|
||||
size_t new_height = 0;
|
||||
if (m_is_icon_set) {
|
||||
new_height = m_icon.height();
|
||||
new_width += m_icon.width();
|
||||
if (m_title.size()) {
|
||||
new_width += 4;
|
||||
}
|
||||
}
|
||||
bounds().set_width(new_width + text_width());
|
||||
bounds().set_height(std::max(new_height, text_height()));
|
||||
}
|
||||
|
||||
size_t Button::text_width()
|
||||
{
|
||||
return Helpers::text_width(m_title, font());
|
||||
}
|
||||
|
||||
void Button::display(LG::Context& ctx, LG::Point<int> pt)
|
||||
{
|
||||
if (m_is_icon_set) {
|
||||
ctx.draw(pt, m_icon);
|
||||
pt.offset_by(m_icon.width() + 4, 0);
|
||||
}
|
||||
|
||||
Helpers::draw_text(ctx, pt, m_title, font());
|
||||
}
|
||||
|
||||
} // namespace WinServer
|
||||
@@ -0,0 +1,46 @@
|
||||
#pragma once
|
||||
|
||||
#include <libg/Color.h>
|
||||
#include <libg/Context.h>
|
||||
#include <string>
|
||||
|
||||
namespace WinServer {
|
||||
|
||||
class Button {
|
||||
public:
|
||||
Button() = default;
|
||||
~Button() = default;
|
||||
|
||||
void display();
|
||||
void set_title(const std::string& title) { m_title = title, recalc_dims(); }
|
||||
void set_title(std::string&& title) { m_title = std::move(title), recalc_dims(); }
|
||||
const std::string& title() const { return m_title; }
|
||||
|
||||
void set_font(const LG::Font& font) { m_font = font, recalc_dims(); }
|
||||
void set_icon(const LG::Glyph& icon) { m_is_icon_set = true, m_icon = icon, recalc_dims(); }
|
||||
|
||||
void set_title_color(const LG::Color& color) { m_title_color = color; }
|
||||
const LG::Color& title_color() const { return m_title_color; }
|
||||
|
||||
inline const LG::Font& font() const { return m_font; }
|
||||
|
||||
inline LG::Rect& bounds() { return m_bounds; }
|
||||
inline const LG::Rect& bounds() const { return m_bounds; }
|
||||
|
||||
void display(LG::Context& ctx, LG::Point<int> pt);
|
||||
|
||||
private:
|
||||
void recalc_dims();
|
||||
size_t text_width();
|
||||
inline size_t text_height() const { return font().size(); }
|
||||
|
||||
LG::Rect m_bounds {};
|
||||
std::string m_title {};
|
||||
LG::Font m_font { LG::Font::system_font() };
|
||||
LG::Color m_title_color;
|
||||
LG::Glyph m_icon;
|
||||
|
||||
bool m_is_icon_set { false };
|
||||
};
|
||||
|
||||
} // namespace WinServer
|
||||
@@ -0,0 +1,30 @@
|
||||
#pragma once
|
||||
|
||||
#include <libg/Color.h>
|
||||
#include <libg/Context.h>
|
||||
#include <libg/Font.h>
|
||||
#include <string_view>
|
||||
|
||||
namespace WinServer {
|
||||
namespace Helpers {
|
||||
|
||||
[[gnu::always_inline]] inline static size_t text_width(const std::string_view& text, const LG::Font& f)
|
||||
{
|
||||
size_t width = 0;
|
||||
|
||||
for (int i = 0; i < text.size(); i++) {
|
||||
width += f.glyph(text[i]).advance();
|
||||
}
|
||||
return width;
|
||||
}
|
||||
|
||||
[[gnu::always_inline]] inline static void draw_text(LG::Context& ctx, LG::Point<int> pt, const std::string_view& text, const LG::Font& f)
|
||||
{
|
||||
for (int i = 0; i < text.size(); i++) {
|
||||
ctx.draw(pt, f.glyph(text[i]));
|
||||
pt.offset_by(f.glyph(text[i]).advance(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Helpers
|
||||
} // namespace WinServer
|
||||
@@ -0,0 +1,95 @@
|
||||
#include "LoadingScreen.h"
|
||||
#include "../Helpers/TextDrawer.h"
|
||||
#include <libfoundation/Memory.h>
|
||||
#include <libg/Context.h>
|
||||
#include <libg/ImageLoaders/PNGLoader.h>
|
||||
#include <unistd.h>
|
||||
|
||||
namespace WinServer {
|
||||
|
||||
LoadingScreen* s_WinServer_LoadingScreen_the = nullptr;
|
||||
|
||||
LoadingScreen::LoadingScreen()
|
||||
: m_screen(Screen::the())
|
||||
{
|
||||
s_WinServer_LoadingScreen_the = this;
|
||||
run_intro_animation();
|
||||
}
|
||||
|
||||
void LoadingScreen::display_status_bar(int progress, int out_of)
|
||||
{
|
||||
LG::Context ctx(m_screen.write_bitmap());
|
||||
int widthp = (progress * progress_line_width()) / out_of;
|
||||
|
||||
ctx.set_fill_color(LG::Color(20, 20, 20));
|
||||
ctx.fill_rounded(LG::Rect(m_progress_line_min_x, m_progress_line_min_y, progress_line_width(), progress_line_height()), LG::CornerMask(4));
|
||||
|
||||
ctx.set_fill_color(LG::Color::White);
|
||||
ctx.fill_rounded(LG::Rect(m_progress_line_min_x, m_progress_line_min_y, widthp, progress_line_height()), LG::CornerMask(4));
|
||||
|
||||
m_screen.swap_buffers();
|
||||
}
|
||||
|
||||
void LoadingScreen::animation_frame(LG::Context& ctx, LG::Point<int> pt, int cur_frame, int total_frames)
|
||||
{
|
||||
// Applying a cubic-easing.
|
||||
int animation_window = total_frames / m_logo_text.size();
|
||||
int animation_frames_per_char = 2 * animation_window;
|
||||
int frame = total_frames - cur_frame;
|
||||
frame = total_frames - frame * frame * frame / (total_frames * total_frames);
|
||||
|
||||
for (int i = 0; i < m_logo_text.size(); i++) {
|
||||
int end_frame = animation_window * (i + 1);
|
||||
int start_frame = std::max(0, end_frame - animation_frames_per_char);
|
||||
|
||||
// Knowing animation bounds for current char and current frame, we calculate of animation
|
||||
// at this frame. This data is used to calcualte alpha and offset for the current char.
|
||||
int animation_progress = std::max(0, std::min(frame - start_frame, animation_frames_per_char));
|
||||
int alpha = (255 * (animation_progress)) / animation_frames_per_char;
|
||||
|
||||
// Offset is calculated based on alpha, maybe we could move out this consts.
|
||||
pt.offset_by((alpha / 30) - 8, 0);
|
||||
|
||||
ctx.set_fill_color(LG::Color(255, 255, 255, alpha));
|
||||
ctx.draw(pt, m_font.glyph(m_logo_text[i]));
|
||||
pt.offset_by(m_font.glyph(m_logo_text[i]).advance(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
void LoadingScreen::run_intro_animation()
|
||||
{
|
||||
const int frames = 102; // 1.7s * 60fps
|
||||
size_t text_height = 64;
|
||||
size_t text_width = Helpers::text_width(m_logo_text, m_font);
|
||||
|
||||
// Preparing area for animation
|
||||
int animation_spread = 10;
|
||||
int content_min_x = m_screen.bounds().mid_x() - (text_width / 2);
|
||||
int content_min_y = m_screen.bounds().mid_y() - ((text_height + progress_line_height()) / 2);
|
||||
auto animation_rect = LG::Rect({ content_min_x - animation_spread, content_min_y, text_width + 2 * animation_spread, text_height });
|
||||
LG::Context ctx(m_screen.display_bitmap());
|
||||
|
||||
for (int frame = 0; frame < frames; frame++) {
|
||||
ctx.set_fill_color(LG::Color::Black);
|
||||
ctx.fill(animation_rect);
|
||||
animation_frame(ctx, { content_min_x, content_min_y }, frame, frames);
|
||||
m_screen.swap_buffers();
|
||||
|
||||
// Copying current progress to backing storage.
|
||||
auto* buf1_ptr = reinterpret_cast<uint32_t*>(&m_screen.display_bitmap()[content_min_y][content_min_x]);
|
||||
auto* buf2_ptr = reinterpret_cast<uint32_t*>(&m_screen.write_bitmap()[content_min_y][content_min_x]);
|
||||
for (int j = 0; j < text_height; j++) {
|
||||
LFoundation::fast_copy(buf2_ptr, buf1_ptr, text_width);
|
||||
buf1_ptr += m_screen.width();
|
||||
buf2_ptr += m_screen.width();
|
||||
}
|
||||
|
||||
// Going to sleep until the next frame.
|
||||
usleep(900000 / 60);
|
||||
}
|
||||
|
||||
m_progress_line_min_x = m_screen.bounds().mid_x() - (progress_line_width() / 2);
|
||||
m_progress_line_min_y = content_min_y + text_height;
|
||||
}
|
||||
|
||||
} // namespace WinServer
|
||||
@@ -0,0 +1,69 @@
|
||||
#pragma once
|
||||
#include "../../Devices/Screen.h"
|
||||
#include <libg/Color.h>
|
||||
#include <libg/Context.h>
|
||||
#include <libg/Font.h>
|
||||
#include <libg/PixelBitmap.h>
|
||||
#include <memory>
|
||||
#include <string_view>
|
||||
|
||||
namespace WinServer {
|
||||
|
||||
template <typename T, int Cost = 1>
|
||||
class AfterComponentLoadProgress {
|
||||
public:
|
||||
static const int cost = Cost;
|
||||
static const size_t progress;
|
||||
};
|
||||
|
||||
static size_t total_cost = 0;
|
||||
template <int Cost>
|
||||
inline size_t __calc_total_cost()
|
||||
{
|
||||
return total_cost += Cost;
|
||||
}
|
||||
|
||||
template <typename T, int Cost>
|
||||
const size_t AfterComponentLoadProgress<T, Cost>::progress = __calc_total_cost<Cost>();
|
||||
|
||||
class LoadingScreen {
|
||||
public:
|
||||
inline static LoadingScreen& the()
|
||||
{
|
||||
extern LoadingScreen* s_WinServer_LoadingScreen_the;
|
||||
return *s_WinServer_LoadingScreen_the;
|
||||
}
|
||||
|
||||
inline static void destroy_the()
|
||||
{
|
||||
extern LoadingScreen* s_WinServer_LoadingScreen_the;
|
||||
delete s_WinServer_LoadingScreen_the;
|
||||
s_WinServer_LoadingScreen_the = nullptr;
|
||||
}
|
||||
|
||||
LoadingScreen();
|
||||
~LoadingScreen() = default;
|
||||
|
||||
template <typename T, int cost = 1>
|
||||
inline void move_progress() { display_status_bar(AfterComponentLoadProgress<T, cost>::progress, total_cost); }
|
||||
|
||||
private:
|
||||
static constexpr int progress_line_height() { return 4; }
|
||||
static constexpr int progress_line_width() { return 128; }
|
||||
|
||||
void display_status_bar(int current_progress, int max_progress);
|
||||
|
||||
void animation_frame(LG::Context& ctx, LG::Point<int> pt, int frame, int total_frames);
|
||||
void run_intro_animation();
|
||||
|
||||
std::string_view m_logo_text { "xOS" };
|
||||
|
||||
Screen& m_screen;
|
||||
int m_progress_line_min_x { 0 };
|
||||
int m_progress_line_min_y { 0 };
|
||||
LG::PixelBitmap m_logo;
|
||||
|
||||
LG::Font& m_font { LG::Font::system_bold_font(32) };
|
||||
};
|
||||
|
||||
} // namespace WinServer
|
||||
@@ -0,0 +1,112 @@
|
||||
#include "MenuBar.h"
|
||||
#include "../../Managers/Compositor.h"
|
||||
#include <libg/ImageLoaders/PNGLoader.h>
|
||||
|
||||
namespace WinServer {
|
||||
|
||||
MenuBar* s_WinServer_MenuBar_the = nullptr;
|
||||
|
||||
MenuBar::MenuBar()
|
||||
: m_background_color(LG::Color::Opaque)
|
||||
, m_bounds(0, 0, Screen::the().bounds().width(), height())
|
||||
, m_popup(Popup::the())
|
||||
{
|
||||
s_WinServer_MenuBar_the = this;
|
||||
LG::PNG::PNGLoader loader;
|
||||
}
|
||||
|
||||
void MenuBar::invalidate_widget(BaseWidget* wg)
|
||||
{
|
||||
for (int i = 0; i < m_widgets.size(); i++) {
|
||||
if (m_widgets[i] == wg) {
|
||||
size_t widget_min_x = widget_start_offset(i);
|
||||
Compositor::the().invalidate(LG::Rect(widget_min_x, 0, m_widgets[i]->width(), height()));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MenuItemAnswer MenuBar::widget_recieve_mouse_status_change(const CursorManager& cursor_manager, size_t wind)
|
||||
{
|
||||
if (wind >= m_widgets.size()) {
|
||||
return MenuItemAnswer::Bad;
|
||||
}
|
||||
|
||||
MenuItemAnswer answer = MenuItemAnswer::Empty;
|
||||
size_t widget_min_x = widget_start_offset(wind);
|
||||
if (cursor_manager.pressed<CursorManager::Params::LeftButton>()) {
|
||||
answer = m_widgets[wind]->mouse_down(cursor_manager.x() - widget_min_x, cursor_manager.y());
|
||||
} else {
|
||||
answer = m_widgets[wind]->mouse_up();
|
||||
}
|
||||
|
||||
if (answer & MenuItemAnswer::Bad) {
|
||||
return answer;
|
||||
}
|
||||
if (answer & MenuItemAnswer::InvalidateMe) {
|
||||
Compositor::the().invalidate(LG::Rect(widget_min_x, 0, m_widgets[wind]->width(), height()));
|
||||
}
|
||||
|
||||
return answer;
|
||||
}
|
||||
|
||||
MenuItemAnswer MenuBar::panel_item_recieve_mouse_status_change(const CursorManager& cursor_manager, size_t ind)
|
||||
{
|
||||
if (!m_menubar_content) {
|
||||
return MenuItemAnswer::Bad;
|
||||
}
|
||||
|
||||
auto& content = *m_menubar_content;
|
||||
if (ind >= content.size()) {
|
||||
return MenuItemAnswer::Bad;
|
||||
}
|
||||
|
||||
MenuItemAnswer answer = MenuItemAnswer::Empty;
|
||||
size_t item_min_x = panel_item_start_offset(ind);
|
||||
if (cursor_manager.pressed<CursorManager::Params::LeftButton>()) {
|
||||
answer = content[ind].mouse_down(cursor_manager.x() - item_min_x, cursor_manager.y());
|
||||
} else {
|
||||
answer = content[ind].mouse_up();
|
||||
}
|
||||
|
||||
if (answer & MenuItemAnswer::Bad) {
|
||||
return answer;
|
||||
}
|
||||
if (answer & MenuItemAnswer::InvalidateMe) {
|
||||
Compositor::the().invalidate(LG::Rect(item_min_x, 0, content[ind].width(), height()));
|
||||
}
|
||||
if (answer & MenuItemAnswer::PopupShow) {
|
||||
popup_will_be_shown(ind);
|
||||
}
|
||||
if (answer & MenuItemAnswer::PopupClose) {
|
||||
popup_will_be_closed();
|
||||
}
|
||||
|
||||
return answer;
|
||||
}
|
||||
|
||||
void MenuBar::invalidate_menubar_panel(Compositor& compositor)
|
||||
{
|
||||
if (menubar_content()) {
|
||||
size_t inv_len = menubar_panel_width(*m_menubar_content);
|
||||
compositor.invalidate(menubar_panel_bounds());
|
||||
}
|
||||
}
|
||||
|
||||
void MenuBar::invalidate_menubar_panel()
|
||||
{
|
||||
invalidate_menubar_panel(Compositor::the());
|
||||
}
|
||||
|
||||
void MenuBar::set_style(StatusBarStyle ts)
|
||||
{
|
||||
set_background_color(ts.color());
|
||||
|
||||
if (ts.dark_text()) {
|
||||
m_text_color = LG::Color::DarkSystemText;
|
||||
} else {
|
||||
m_text_color = LG::Color::LightSystemText;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace WinServer
|
||||
266
userland/servers/window_server/src/Components/MenuBar/MenuBar.h
Normal file
266
userland/servers/window_server/src/Components/MenuBar/MenuBar.h
Normal file
@@ -0,0 +1,266 @@
|
||||
#pragma once
|
||||
#include "../../Managers/CursorManager.h"
|
||||
#include "MenuItem.h"
|
||||
#include "Widgets/BaseWidget.h"
|
||||
#include <libapi/window_server/MessageContent/MenuBar.h>
|
||||
#include <libfoundation/EventLoop.h>
|
||||
#include <libfoundation/Logger.h>
|
||||
#include <libg/Context.h>
|
||||
#include <libg/PixelBitmap.h>
|
||||
#include <libg/Point.h>
|
||||
#include <vector>
|
||||
|
||||
namespace WinServer {
|
||||
|
||||
struct PopupContext {
|
||||
|
||||
void discard() { opened = false; }
|
||||
|
||||
bool opened { false };
|
||||
int invoker_id { 0 };
|
||||
};
|
||||
|
||||
class WindowManager;
|
||||
|
||||
class MenuBar {
|
||||
friend WindowManager;
|
||||
|
||||
public:
|
||||
inline static MenuBar& the()
|
||||
{
|
||||
extern MenuBar* s_WinServer_MenuBar_the;
|
||||
return *s_WinServer_MenuBar_the;
|
||||
}
|
||||
|
||||
MenuBar();
|
||||
|
||||
static constexpr size_t height() { return 24; }
|
||||
static constexpr size_t padding() { return 8; }
|
||||
static constexpr size_t menubar_content_offset() { return padding() + 2; }
|
||||
static constexpr int popup_x_offset() { return -8; }
|
||||
static constexpr int text_y_offset() { return 2; }
|
||||
|
||||
void set_background_color(const LG::Color& clr) { m_background_color = clr; }
|
||||
void set_style(StatusBarStyle style);
|
||||
|
||||
size_t width() const { return m_bounds.width(); }
|
||||
LG::Rect& bounds() { return m_bounds; }
|
||||
const LG::Rect& bounds() const { return m_bounds; }
|
||||
|
||||
template <class T, class... Args>
|
||||
T& add_widget(Args&&... args)
|
||||
{
|
||||
T* widget = new T(args...);
|
||||
m_widgets.push_back(widget);
|
||||
return *widget;
|
||||
}
|
||||
|
||||
inline LFoundation::EventLoop& event_loop() { return LFoundation::EventLoop::the(); }
|
||||
void invalidate_widget(BaseWidget* wg);
|
||||
|
||||
inline bool is_hovered() const { return m_hovered; }
|
||||
inline PopupContext& popup_context() { return m_popup_context; }
|
||||
inline const PopupContext& popup_context() const { return m_popup_context; }
|
||||
inline LG::Rect menubar_panel_bounds() { return LG::Rect(menubar_content_offset() + popup_x_offset(), text_y_offset(), menubar_panel_width(*m_menubar_content) - popup_x_offset(), height() - 3); }
|
||||
|
||||
void draw_panel_items(LG::Context& ctx);
|
||||
void draw_widgets(LG::Context& ctx);
|
||||
[[gnu::always_inline]] inline void draw(LG::Context& ctx)
|
||||
{
|
||||
ctx.set_fill_color(m_background_color);
|
||||
ctx.fill({ 0, 0, MenuBar::width(), MenuBar::height() });
|
||||
|
||||
ctx.set_fill_color(LG::Color::LightSystemOpaque);
|
||||
if (m_menubar_content) {
|
||||
draw_panel_items(ctx);
|
||||
}
|
||||
|
||||
draw_widgets(ctx);
|
||||
}
|
||||
|
||||
inline void on_mouse_move(const CursorManager& cursor_manager) { m_hovered = true; }
|
||||
inline void on_mouse_leave(const CursorManager& cursor_manager) { m_hovered = false; }
|
||||
|
||||
void on_mouse_status_change(const CursorManager& cursor_manager);
|
||||
|
||||
inline std::vector<MenuDir>* menubar_content() const { return m_menubar_content; }
|
||||
|
||||
void set_menubar_content(std::vector<MenuDir>* mc);
|
||||
void set_menubar_content(std::vector<MenuDir>* mc, Compositor& compositor);
|
||||
|
||||
inline void popup_will_be_shown(int invoker_id)
|
||||
{
|
||||
auto& content = *m_menubar_content;
|
||||
popup_context().invoker_id = invoker_id;
|
||||
m_popup.show({ (int)panel_item_start_offset(invoker_id) + popup_x_offset(), height() + 2 }, content[invoker_id].items());
|
||||
}
|
||||
|
||||
inline void popup_will_be_closed()
|
||||
{
|
||||
popup_context().discard();
|
||||
m_popup.hide();
|
||||
}
|
||||
|
||||
private:
|
||||
// Widgets
|
||||
MenuItemAnswer widget_recieve_mouse_status_change(const CursorManager& cursor_manager, size_t wind);
|
||||
size_t widget_start_offset(size_t index);
|
||||
int find_widget(int x, int y);
|
||||
|
||||
// MenuBar Panel
|
||||
void invalidate_menubar_panel();
|
||||
void invalidate_menubar_panel(Compositor& compositor);
|
||||
MenuItemAnswer panel_item_recieve_mouse_status_change(const CursorManager& cursor_manager, size_t ind);
|
||||
static inline size_t menubar_panel_width(const std::vector<MenuDir>& items)
|
||||
{
|
||||
size_t width = 0;
|
||||
for (int ind = 0; ind < items.size(); ind++) {
|
||||
width += padding();
|
||||
width += items[ind].width();
|
||||
}
|
||||
return width;
|
||||
}
|
||||
|
||||
size_t panel_item_start_offset(size_t index);
|
||||
int find_menubar_panel_item(int x, int y);
|
||||
|
||||
LG::Rect m_bounds;
|
||||
std::vector<MenuDir>* m_menubar_content { nullptr };
|
||||
std::vector<BaseWidget*> m_widgets;
|
||||
Popup& m_popup;
|
||||
PopupContext m_popup_context;
|
||||
LG::Color m_background_color;
|
||||
LG::Color m_text_color;
|
||||
|
||||
bool m_hovered { false };
|
||||
};
|
||||
|
||||
// Implementation
|
||||
|
||||
inline void MenuBar::draw_panel_items(LG::Context& ctx)
|
||||
{
|
||||
if (!m_menubar_content) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto offset = ctx.draw_offset();
|
||||
size_t start_offset = menubar_content_offset();
|
||||
auto& content = *m_menubar_content;
|
||||
|
||||
for (int ind = 0; ind < content.size(); ind++) {
|
||||
ctx.set_fill_color(m_text_color);
|
||||
ctx.set_draw_offset(LG::Point<int>(start_offset, text_y_offset()));
|
||||
content[ind].draw(ctx);
|
||||
start_offset += content[ind].width();
|
||||
start_offset += padding();
|
||||
}
|
||||
|
||||
ctx.set_draw_offset(offset);
|
||||
}
|
||||
|
||||
inline void MenuBar::draw_widgets(LG::Context& ctx)
|
||||
{
|
||||
auto offset = ctx.draw_offset();
|
||||
size_t start_offset = MenuBar::width() - padding();
|
||||
|
||||
for (int wind = m_widgets.size() - 1; wind >= 0; wind--) {
|
||||
ctx.set_fill_color(m_text_color);
|
||||
start_offset -= padding();
|
||||
start_offset -= m_widgets[wind]->width();
|
||||
ctx.set_draw_offset(LG::Point<int>(start_offset, text_y_offset()));
|
||||
m_widgets[wind]->draw(ctx);
|
||||
}
|
||||
|
||||
ctx.set_draw_offset(offset);
|
||||
}
|
||||
|
||||
inline void MenuBar::on_mouse_status_change(const CursorManager& cursor_manager)
|
||||
{
|
||||
// Checking recievers
|
||||
int target_widget = find_widget(cursor_manager.x(), cursor_manager.y());
|
||||
if (target_widget >= 0) {
|
||||
widget_recieve_mouse_status_change(cursor_manager, (size_t)target_widget);
|
||||
return;
|
||||
}
|
||||
|
||||
int target_panel_item = find_menubar_panel_item(cursor_manager.x(), cursor_manager.y());
|
||||
if (target_panel_item >= 0) {
|
||||
panel_item_recieve_mouse_status_change(cursor_manager, (size_t)target_panel_item);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
inline void MenuBar::set_menubar_content(std::vector<MenuDir>* mc)
|
||||
{
|
||||
invalidate_menubar_panel();
|
||||
m_menubar_content = mc;
|
||||
invalidate_menubar_panel();
|
||||
popup_will_be_closed();
|
||||
}
|
||||
|
||||
inline void MenuBar::set_menubar_content(std::vector<MenuDir>* mc, Compositor& compositor)
|
||||
{
|
||||
invalidate_menubar_panel(compositor);
|
||||
m_menubar_content = mc;
|
||||
invalidate_menubar_panel(compositor);
|
||||
popup_will_be_closed();
|
||||
}
|
||||
|
||||
inline size_t MenuBar::widget_start_offset(size_t index)
|
||||
{
|
||||
size_t start_offset = MenuBar::width() - padding();
|
||||
for (int wind = m_widgets.size() - 1; wind >= (int)index; wind--) {
|
||||
start_offset -= padding();
|
||||
start_offset -= m_widgets[wind]->width();
|
||||
}
|
||||
return start_offset;
|
||||
}
|
||||
|
||||
inline int MenuBar::find_widget(int x, int y)
|
||||
{
|
||||
size_t start_offset = MenuBar::width() - padding();
|
||||
for (int wind = m_widgets.size() - 1; wind >= 0; wind--) {
|
||||
start_offset -= padding();
|
||||
int end_offset = start_offset;
|
||||
start_offset -= m_widgets[wind]->width();
|
||||
if (start_offset <= x && x <= end_offset) {
|
||||
return wind;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
inline size_t MenuBar::panel_item_start_offset(size_t index)
|
||||
{
|
||||
if (!m_menubar_content) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto& content = *m_menubar_content;
|
||||
size_t start_offset = menubar_content_offset();
|
||||
for (int ind = 0; ind < index; ind++) {
|
||||
start_offset += padding();
|
||||
start_offset += content[ind].width();
|
||||
}
|
||||
return start_offset;
|
||||
}
|
||||
|
||||
inline int MenuBar::find_menubar_panel_item(int x, int y)
|
||||
{
|
||||
if (!m_menubar_content) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
auto& content = *m_menubar_content;
|
||||
size_t start_offset = menubar_content_offset();
|
||||
for (int ind = 0; ind < content.size(); ind++) {
|
||||
int end_offset = start_offset + content[ind].width();
|
||||
if (start_offset <= x && x <= end_offset) {
|
||||
return ind;
|
||||
}
|
||||
start_offset = end_offset + padding();
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
} // namespace WinServer
|
||||
@@ -0,0 +1,61 @@
|
||||
#pragma once
|
||||
#include "../Helpers/TextDrawer.h"
|
||||
#include "../Popup/Popup.h"
|
||||
#include "MenuItemAnswer.h"
|
||||
#include <libfoundation/Logger.h>
|
||||
#include <libg/Context.h>
|
||||
#include <libg/Font.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace WinServer {
|
||||
|
||||
class MenuDir {
|
||||
public:
|
||||
MenuDir(const std::string& title, int id)
|
||||
: m_title(title)
|
||||
, m_id(id)
|
||||
{
|
||||
}
|
||||
|
||||
~MenuDir() = default;
|
||||
|
||||
inline void set_font(LG::Font& f) { m_font = f; }
|
||||
inline void add_item(PopupItem&& item) { m_items.push_back(std::move(item)); }
|
||||
inline void add_item(const PopupItem& item) { m_items.push_back(item); }
|
||||
|
||||
inline int id() const { return m_id; }
|
||||
inline void set_title(const std::string& title) { m_title = title; }
|
||||
inline void set_title(std::string&& title) { m_title = std::move(title); }
|
||||
inline const std::string& title() const { return m_title; }
|
||||
inline const PopupData& items() const { return m_items; }
|
||||
inline PopupData& items() { return m_items; }
|
||||
|
||||
inline size_t width() const { return Helpers::text_width(m_title, m_font); }
|
||||
|
||||
[[gnu::always_inline]] inline void draw(LG::Context& ctx)
|
||||
{
|
||||
ctx.set_fill_color(LG::Color::Black);
|
||||
Helpers::draw_text(ctx, { 0, 6 }, m_title, m_font);
|
||||
}
|
||||
|
||||
inline MenuItemAnswer mouse_down(int x, int y)
|
||||
{
|
||||
m_active = true;
|
||||
return MenuItemAnswer(MenuItemAnswer::InvalidateMe | MenuItemAnswer::PopupShow);
|
||||
}
|
||||
|
||||
inline MenuItemAnswer mouse_up()
|
||||
{
|
||||
return MenuItemAnswer::InvalidateMe;
|
||||
}
|
||||
|
||||
private:
|
||||
int m_id { -1 };
|
||||
bool m_active { false };
|
||||
std::string m_title;
|
||||
LG::Font& m_font { LG::Font::system_font() };
|
||||
PopupData m_items;
|
||||
};
|
||||
|
||||
} // namespace WinServer
|
||||
@@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
namespace WinServer {
|
||||
|
||||
// Common for widget and panel item.
|
||||
enum MenuItemAnswer {
|
||||
Empty = 0x0,
|
||||
Bad = 0x1, // Bad mark
|
||||
InvalidateMe = 0x2, // Asks to invalidate menu item
|
||||
PopupShow = 0x4, // Asks to show popup. MenuBar will call popup_rect()
|
||||
PopupClose = 0x8, // Asks to close popup.
|
||||
};
|
||||
|
||||
}; // namespace WinServer
|
||||
@@ -0,0 +1,20 @@
|
||||
#pragma once
|
||||
#include "../MenuItemAnswer.h"
|
||||
#include <libg/Context.h>
|
||||
|
||||
namespace WinServer {
|
||||
|
||||
class BaseWidget {
|
||||
public:
|
||||
BaseWidget() = default;
|
||||
virtual ~BaseWidget() = default;
|
||||
|
||||
size_t height() { return 20; }
|
||||
virtual size_t width() { return 0; }
|
||||
virtual void draw(LG::Context& ctx) { }
|
||||
virtual MenuItemAnswer mouse_down(int x, int y) { return MenuItemAnswer::Empty; }
|
||||
virtual MenuItemAnswer mouse_up() { return MenuItemAnswer::Empty; }
|
||||
virtual void popup_rect(LG::Rect& r) { }
|
||||
};
|
||||
|
||||
} // namespace WinServer
|
||||
@@ -0,0 +1,69 @@
|
||||
#pragma once
|
||||
#include "../../../Helpers/TextDrawer.h"
|
||||
#include "../../MenuBar.h"
|
||||
#include "../BaseWidget.h"
|
||||
#include <ctime>
|
||||
#include <libfoundation/Logger.h>
|
||||
#include <libg/Font.h>
|
||||
|
||||
#define DATA_BUF 32
|
||||
|
||||
namespace WinServer {
|
||||
|
||||
class Clock : public BaseWidget {
|
||||
public:
|
||||
Clock()
|
||||
{
|
||||
m_date = static_cast<char*>(malloc(DATA_BUF));
|
||||
memset(m_date, 0, DATA_BUF);
|
||||
update_time();
|
||||
MenuBar::the().event_loop().add(LFoundation::Timer([this] {
|
||||
this->update_time();
|
||||
},
|
||||
5000, LFoundation::Timer::Repeat));
|
||||
}
|
||||
|
||||
~Clock()
|
||||
{
|
||||
free(m_date);
|
||||
}
|
||||
|
||||
void update_time()
|
||||
{
|
||||
std::time_t new_time = std::time(nullptr);
|
||||
if (new_time / 60 != cur_time / 60) {
|
||||
cur_time = new_time;
|
||||
std::strftime(m_date, DATA_BUF, "%a %R", std::localtime(&cur_time));
|
||||
MenuBar::the().invalidate_widget(this);
|
||||
}
|
||||
}
|
||||
|
||||
size_t width() override { return 50; }
|
||||
void draw(LG::Context& ctx) override
|
||||
{
|
||||
size_t twidth = Helpers::text_width(m_date, m_font);
|
||||
Helpers::draw_text(ctx, { int(width() - twidth) / 2, 6 }, m_date, m_font);
|
||||
}
|
||||
|
||||
MenuItemAnswer mouse_down(int x, int y) override
|
||||
{
|
||||
m_clicked = true;
|
||||
return MenuItemAnswer::InvalidateMe;
|
||||
}
|
||||
|
||||
MenuItemAnswer mouse_up() override
|
||||
{
|
||||
m_clicked = false;
|
||||
return MenuItemAnswer::InvalidateMe;
|
||||
}
|
||||
|
||||
void popup_rect(LG::Rect& r) override { }
|
||||
|
||||
private:
|
||||
bool m_clicked { false };
|
||||
char* m_date { nullptr };
|
||||
std::time_t cur_time { 0 };
|
||||
LG::Font& m_font { LG::Font::system_font() };
|
||||
};
|
||||
|
||||
} // namespace WinServer
|
||||
@@ -0,0 +1,50 @@
|
||||
#pragma once
|
||||
#include "../../../Helpers/TextDrawer.h"
|
||||
#include "../../MenuBar.h"
|
||||
#include "../BaseWidget.h"
|
||||
#include <ctime>
|
||||
#include <libfoundation/Logger.h>
|
||||
#include <libg/Font.h>
|
||||
#include <libg/ImageLoaders/PNGLoader.h>
|
||||
|
||||
#define DATA_BUF 32
|
||||
|
||||
namespace WinServer {
|
||||
|
||||
class ControlPanelToggle : public BaseWidget {
|
||||
public:
|
||||
ControlPanelToggle()
|
||||
{
|
||||
LG::PNG::PNGLoader loader;
|
||||
m_icon = loader.load_from_file("/res/system/contol_center.png");
|
||||
}
|
||||
|
||||
~ControlPanelToggle() = default;
|
||||
|
||||
constexpr size_t width() override { return 20; }
|
||||
constexpr size_t height() { return 12; }
|
||||
void draw(LG::Context& ctx) override
|
||||
{
|
||||
ctx.draw({ 4, 4 }, m_icon);
|
||||
}
|
||||
|
||||
MenuItemAnswer mouse_down(int x, int y) override
|
||||
{
|
||||
m_clicked = true;
|
||||
return MenuItemAnswer::InvalidateMe;
|
||||
}
|
||||
|
||||
MenuItemAnswer mouse_up() override
|
||||
{
|
||||
m_clicked = false;
|
||||
return MenuItemAnswer::InvalidateMe;
|
||||
}
|
||||
|
||||
void popup_rect(LG::Rect& r) override { }
|
||||
|
||||
private:
|
||||
LG::PixelBitmap m_icon;
|
||||
bool m_clicked { false };
|
||||
};
|
||||
|
||||
} // namespace WinServer
|
||||
@@ -0,0 +1,73 @@
|
||||
#include "Popup.h"
|
||||
#include "../../Constants/Colors.h"
|
||||
#include "../../Managers/WindowManager.h"
|
||||
#include "../Helpers/TextDrawer.h"
|
||||
#include <algorithm>
|
||||
|
||||
namespace WinServer {
|
||||
|
||||
Popup* s_WinServer_Popup_the = nullptr;
|
||||
|
||||
Popup::Popup()
|
||||
{
|
||||
s_WinServer_Popup_the = this;
|
||||
}
|
||||
|
||||
void Popup::on_set_data()
|
||||
{
|
||||
size_t max_width = 0;
|
||||
size_t max_height = 0;
|
||||
for (auto& item : m_data) {
|
||||
max_width = std::max(max_width, Helpers::text_width(item.text, m_font));
|
||||
max_height += m_font.size() + spacing();
|
||||
}
|
||||
max_width = std::max(max_width, min_width());
|
||||
bounds().set_width(max_width + 2 * spacing());
|
||||
bounds().set_height(max_height + spacing());
|
||||
}
|
||||
|
||||
void Popup::draw(LG::Context& ctx)
|
||||
{
|
||||
if (!visible()) {
|
||||
return;
|
||||
}
|
||||
|
||||
ctx.set_fill_color(LG::Color::LightSystemOpaque);
|
||||
ctx.fill_rounded(bounds(), LG::CornerMask(LG::CornerMask::SystemRadius));
|
||||
|
||||
ctx.set_fill_color(Color::Shadow);
|
||||
ctx.draw_box_shading(bounds(), LG::Shading(LG::Shading::Type::Box, 0, LG::Shading::SystemSpread), LG::CornerMask(LG::CornerMask::SystemRadius));
|
||||
|
||||
const size_t line_height = (m_font.size() + spacing());
|
||||
int height = bounds().min_y() + spacing();
|
||||
|
||||
for (int i = 0; i < m_data.size(); i++) {
|
||||
if (i == m_hovered_item) {
|
||||
ctx.set_fill_color(LG::Color::White);
|
||||
ctx.fill_rounded(LG::Rect(bounds().min_x() + 4, height - spacing() / 2, bounds().width() - 8, line_height), LG::CornerMask(2));
|
||||
}
|
||||
ctx.set_fill_color(LG::Color::DarkSystemText);
|
||||
Helpers::draw_text(ctx, { bounds().min_x() + spacing(), height }, m_data[i].text, m_font);
|
||||
height += line_height;
|
||||
}
|
||||
}
|
||||
|
||||
void Popup::set_preferred_origin(const LG::Point<int>& origin)
|
||||
{
|
||||
auto& wm = WindowManager::the();
|
||||
LG::Point<int> pos;
|
||||
int x = origin.x();
|
||||
int y = origin.y();
|
||||
|
||||
if (origin.y() + bounds().height() > wm.visible_area().max_y()) {
|
||||
y = origin.y() - bounds().height();
|
||||
}
|
||||
if (origin.x() + bounds().width() > wm.visible_area().max_x()) {
|
||||
x = origin.x() - bounds().width();
|
||||
}
|
||||
pos.set_x(x);
|
||||
pos.set_y(y);
|
||||
m_bounds.set_origin(std::move(pos));
|
||||
}
|
||||
|
||||
} // namespace WinServer
|
||||
147
userland/servers/window_server/src/Components/Popup/Popup.h
Normal file
147
userland/servers/window_server/src/Components/Popup/Popup.h
Normal file
@@ -0,0 +1,147 @@
|
||||
#pragma once
|
||||
|
||||
#include "../../Managers/Compositor.h"
|
||||
#include "../../Managers/CursorManager.h"
|
||||
#include <functional>
|
||||
#include <libg/Color.h>
|
||||
#include <libg/Context.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace WinServer {
|
||||
|
||||
struct PopupItem {
|
||||
int id;
|
||||
std::string text;
|
||||
std::function<void(int)> callback;
|
||||
|
||||
static const int InternalId = -1;
|
||||
};
|
||||
using PopupData = std::vector<PopupItem>;
|
||||
|
||||
class Popup {
|
||||
public:
|
||||
inline static Popup& the()
|
||||
{
|
||||
extern Popup* s_WinServer_Popup_the;
|
||||
return *s_WinServer_Popup_the;
|
||||
}
|
||||
|
||||
Popup();
|
||||
~Popup() = default;
|
||||
|
||||
constexpr int spacing() const { return 8; }
|
||||
constexpr size_t min_width() const { return 180u; }
|
||||
|
||||
void set_preferred_origin(const LG::Point<int>& origin);
|
||||
|
||||
inline LG::Rect& bounds() { return m_bounds; }
|
||||
inline const LG::Rect& bounds() const { return m_bounds; }
|
||||
|
||||
inline LG::Rect draw_frame() const
|
||||
{
|
||||
const int shadow_spread = LG::Shading::SystemSpread;
|
||||
return LG::Rect(bounds().min_x() - shadow_spread, bounds().min_y() - shadow_spread, bounds().width() + 2 * shadow_spread, bounds().height() + 2 * shadow_spread);
|
||||
}
|
||||
|
||||
inline void set_visible(bool vis)
|
||||
{
|
||||
if (m_visible != vis) {
|
||||
Compositor::the().invalidate(draw_frame());
|
||||
}
|
||||
m_visible = vis;
|
||||
}
|
||||
inline bool visible() const { return m_visible; }
|
||||
|
||||
void show(const LG::Point<int>& origin, PopupData& data)
|
||||
{
|
||||
set_visible(false);
|
||||
set_data(data);
|
||||
set_preferred_origin(origin);
|
||||
set_visible(true);
|
||||
}
|
||||
|
||||
void hide()
|
||||
{
|
||||
m_hovered_item = HoveredItem::No;
|
||||
set_visible(false);
|
||||
}
|
||||
|
||||
void on_set_data();
|
||||
void set_data(const PopupData& data) { m_data = data, on_set_data(); }
|
||||
void set_data(PopupData&& data) { m_data = std::move(data), on_set_data(); }
|
||||
|
||||
void on_mouse_move(const CursorManager& cursor_manager)
|
||||
{
|
||||
// A simple implemetation to get hover effect and clicks.
|
||||
if (!visible()) {
|
||||
return;
|
||||
}
|
||||
|
||||
size_t data_size = m_data.size();
|
||||
const size_t line_height = (m_font.size() + 8);
|
||||
int prev_hovered_item = m_hovered_item;
|
||||
int rel_y = cursor_manager.y() - bounds().min_y();
|
||||
rel_y -= 4; // Offset of the first element;
|
||||
m_hovered_item = rel_y / line_height;
|
||||
|
||||
if (m_hovered_item >= data_size) {
|
||||
m_hovered_item = HoveredItem::No;
|
||||
}
|
||||
|
||||
if (m_hovered_item != prev_hovered_item && prev_hovered_item != HoveredItem::No) {
|
||||
Compositor::the().invalidate(LG::Rect(bounds().min_x(), bounds().min_y() + 4 + line_height * prev_hovered_item, bounds().width(), line_height));
|
||||
}
|
||||
|
||||
if (m_hovered_item != prev_hovered_item && m_hovered_item != HoveredItem::No) {
|
||||
Compositor::the().invalidate(LG::Rect(bounds().min_x(), bounds().min_y() + 4 + line_height * m_hovered_item, bounds().width(), line_height));
|
||||
}
|
||||
}
|
||||
|
||||
inline void on_mouse_status_change(const CursorManager& cursor_manager)
|
||||
{
|
||||
if (m_hovered_item == HoveredItem::No) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!visible()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto& data = m_data;
|
||||
if (cursor_manager.pressed<CursorManager::Params::LeftButton>()) {
|
||||
data[m_hovered_item].callback((int)data[m_hovered_item].id);
|
||||
hide();
|
||||
}
|
||||
}
|
||||
|
||||
void on_mouse_leave(const CursorManager& cursor_manager)
|
||||
{
|
||||
if (m_hovered_item == HoveredItem::No) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!visible()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const size_t line_height = (m_font.size() + 8);
|
||||
Compositor::the().invalidate(LG::Rect(bounds().min_x(), bounds().min_y() + 4 + line_height * m_hovered_item, bounds().width(), line_height));
|
||||
m_hovered_item = HoveredItem::No;
|
||||
}
|
||||
|
||||
void draw(LG::Context& ctx);
|
||||
|
||||
private:
|
||||
enum HoveredItem {
|
||||
No = -1,
|
||||
};
|
||||
|
||||
LG::Rect m_bounds { 0, 0, 0, 0 };
|
||||
bool m_visible { false };
|
||||
PopupData m_data;
|
||||
int m_hovered_item { HoveredItem::No };
|
||||
LG::Font& m_font { LG::Font::system_font() };
|
||||
};
|
||||
|
||||
} // namespace WinServer
|
||||
@@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
namespace WinServer {
|
||||
|
||||
enum class ViolationClass {
|
||||
Ignorable,
|
||||
Moderate,
|
||||
Serious,
|
||||
};
|
||||
|
||||
} // namespace WinServer
|
||||
12
userland/servers/window_server/src/Constants/Colors.h
Normal file
12
userland/servers/window_server/src/Constants/Colors.h
Normal file
@@ -0,0 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
namespace WinServer {
|
||||
|
||||
enum Color {
|
||||
Shadow = 0xf4000000,
|
||||
InactiveText = 0x7B7E78,
|
||||
};
|
||||
|
||||
} // namespace WinServer
|
||||
35
userland/servers/window_server/src/Devices/Devices.cpp
Normal file
35
userland/servers/window_server/src/Devices/Devices.cpp
Normal file
@@ -0,0 +1,35 @@
|
||||
#include "Devices.h"
|
||||
#include <fcntl.h>
|
||||
#include <libfoundation/Logger.h>
|
||||
|
||||
namespace WinServer {
|
||||
|
||||
Devices* s_WinServer_Devices_the = nullptr;
|
||||
|
||||
Devices::Devices()
|
||||
{
|
||||
s_WinServer_Devices_the = this;
|
||||
m_mouse_fd = open("/dev/mouse", O_RDONLY);
|
||||
if (m_mouse_fd < 0) {
|
||||
Logger::debug << "Can't open mouse" << std::endl;
|
||||
} else {
|
||||
LFoundation::EventLoop::the().add(
|
||||
m_mouse_fd, [] {
|
||||
Devices::the().pump_mouse();
|
||||
},
|
||||
nullptr);
|
||||
}
|
||||
|
||||
m_keyboard_fd = open("/dev/kbd", O_RDONLY);
|
||||
if (m_keyboard_fd < 0) {
|
||||
Logger::debug << "Can't open keyboard" << std::endl;
|
||||
} else {
|
||||
LFoundation::EventLoop::the().add(
|
||||
m_keyboard_fd, [] {
|
||||
Devices::the().pump_keyboard();
|
||||
},
|
||||
nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace WinServer
|
||||
59
userland/servers/window_server/src/Devices/Devices.h
Normal file
59
userland/servers/window_server/src/Devices/Devices.h
Normal file
@@ -0,0 +1,59 @@
|
||||
#pragma once
|
||||
#include "../IPC/Event.h"
|
||||
#include "../Managers/WindowManager.h"
|
||||
#include <libfoundation/EventLoop.h>
|
||||
#include <memory>
|
||||
|
||||
namespace WinServer {
|
||||
|
||||
class Devices {
|
||||
public:
|
||||
inline static Devices& the()
|
||||
{
|
||||
extern Devices* s_WinServer_Devices_the;
|
||||
return *s_WinServer_Devices_the;
|
||||
}
|
||||
|
||||
Devices();
|
||||
~Devices() = default;
|
||||
|
||||
inline void pump_mouse() const
|
||||
{
|
||||
LFoundation::EventLoop& el = LFoundation::EventLoop::the();
|
||||
WindowManager& wm = WindowManager::the();
|
||||
|
||||
char buf[512];
|
||||
int read_cnt = read(m_mouse_fd, buf, sizeof(buf));
|
||||
if (read_cnt <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto* packet_buf = reinterpret_cast<MousePacket*>(buf);
|
||||
for (int offset = 0, cnt = 0; offset < read_cnt; offset += sizeof(MousePacket), cnt++) {
|
||||
el.add(wm, new MouseEvent(packet_buf[cnt]));
|
||||
}
|
||||
}
|
||||
|
||||
inline void pump_keyboard() const
|
||||
{
|
||||
LFoundation::EventLoop& el = LFoundation::EventLoop::the();
|
||||
WindowManager& wm = WindowManager::the();
|
||||
|
||||
char buf[512];
|
||||
int read_cnt = read(m_keyboard_fd, buf, sizeof(buf));
|
||||
if (read_cnt <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto* packet_buf = reinterpret_cast<KeyboardPacket*>(buf);
|
||||
for (int offset = 0, cnt = 0; offset < read_cnt; offset += sizeof(KeyboardPacket), cnt++) {
|
||||
el.add(wm, new KeyboardEvent(packet_buf[cnt]));
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
int m_mouse_fd;
|
||||
int m_keyboard_fd;
|
||||
};
|
||||
|
||||
} // namespace WinServer
|
||||
42
userland/servers/window_server/src/Devices/Screen.cpp
Normal file
42
userland/servers/window_server/src/Devices/Screen.cpp
Normal file
@@ -0,0 +1,42 @@
|
||||
#include "Screen.h"
|
||||
#include "../Managers/Compositor.h"
|
||||
#include <cstring>
|
||||
#include <fcntl.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/mman.h>
|
||||
#include <utility>
|
||||
|
||||
namespace WinServer {
|
||||
|
||||
Screen* s_WinServer_Screen_the = nullptr;
|
||||
|
||||
Screen::Screen()
|
||||
: m_depth(4)
|
||||
, m_write_bitmap()
|
||||
, m_display_bitmap()
|
||||
{
|
||||
s_WinServer_Screen_the = this;
|
||||
m_screen_fd = open("/dev/bga", O_RDWR);
|
||||
m_bounds = LG::Rect(0, 0, ioctl(m_screen_fd, BGA_GET_WIDTH, 0), ioctl(m_screen_fd, BGA_GET_HEIGHT, 0));
|
||||
|
||||
size_t screen_buffer_size = width() * height() * depth();
|
||||
auto* first_buffer = reinterpret_cast<LG::Color*>(mmap(NULL, 1, PROT_READ | PROT_WRITE, MAP_SHARED, m_screen_fd, 0));
|
||||
auto* second_buffer = reinterpret_cast<LG::Color*>(reinterpret_cast<uint8_t*>(first_buffer) + screen_buffer_size);
|
||||
|
||||
m_display_bitmap = LG::PixelBitmap(first_buffer, width(), height());
|
||||
m_write_bitmap = LG::PixelBitmap(second_buffer, width(), height());
|
||||
|
||||
m_display_bitmap_ptr = &m_display_bitmap;
|
||||
m_write_bitmap_ptr = &m_write_bitmap;
|
||||
|
||||
m_active_buffer = 0;
|
||||
}
|
||||
|
||||
void Screen::swap_buffers()
|
||||
{
|
||||
m_write_bitmap_ptr.swap(m_display_bitmap_ptr);
|
||||
m_active_buffer ^= 1;
|
||||
ioctl(m_screen_fd, BGA_SWAP_BUFFERS, m_active_buffer);
|
||||
}
|
||||
|
||||
} // namespace WinServer
|
||||
45
userland/servers/window_server/src/Devices/Screen.h
Normal file
45
userland/servers/window_server/src/Devices/Screen.h
Normal file
@@ -0,0 +1,45 @@
|
||||
#pragma once
|
||||
#include <libg/Color.h>
|
||||
#include <libg/PixelBitmap.h>
|
||||
#include <memory>
|
||||
|
||||
namespace WinServer {
|
||||
|
||||
class Screen {
|
||||
public:
|
||||
inline static Screen& the()
|
||||
{
|
||||
extern Screen* s_WinServer_Screen_the;
|
||||
return *s_WinServer_Screen_the;
|
||||
}
|
||||
|
||||
Screen();
|
||||
|
||||
void swap_buffers();
|
||||
|
||||
inline size_t width() { return m_bounds.width(); }
|
||||
inline size_t height() const { return m_bounds.height(); }
|
||||
inline LG::Rect& bounds() { return m_bounds; }
|
||||
inline const LG::Rect& bounds() const { return m_bounds; }
|
||||
inline uint32_t depth() const { return m_depth; }
|
||||
|
||||
inline LG::PixelBitmap& write_bitmap() { return *m_write_bitmap_ptr; }
|
||||
inline const LG::PixelBitmap& write_bitmap() const { return *m_write_bitmap_ptr; }
|
||||
inline LG::PixelBitmap& display_bitmap() { return *m_display_bitmap_ptr; }
|
||||
inline const LG::PixelBitmap& display_bitmap() const { return *m_display_bitmap_ptr; }
|
||||
|
||||
private:
|
||||
int m_screen_fd;
|
||||
LG::Rect m_bounds;
|
||||
uint32_t m_depth;
|
||||
|
||||
int m_active_buffer;
|
||||
|
||||
LG::PixelBitmap m_write_bitmap;
|
||||
LG::PixelBitmap m_display_bitmap;
|
||||
|
||||
std::unique_ptr<LG::PixelBitmap> m_write_bitmap_ptr { nullptr };
|
||||
std::unique_ptr<LG::PixelBitmap> m_display_bitmap_ptr { nullptr };
|
||||
};
|
||||
|
||||
} // namespace WinServer
|
||||
46
userland/servers/window_server/src/IPC/Connection.cpp
Normal file
46
userland/servers/window_server/src/IPC/Connection.cpp
Normal file
@@ -0,0 +1,46 @@
|
||||
#include "Connection.h"
|
||||
#include "Event.h"
|
||||
#include <libfoundation/EventLoop.h>
|
||||
#include <sys/socket.h>
|
||||
|
||||
#define WINSERVER_REQUEST_SOCKET_PATH "/tmp/winserver_requests.sock"
|
||||
#define WINSERVER_REQUEST_SOCKET_PATH_SIZE sizeof(WINSERVER_REQUEST_SOCKET_PATH)
|
||||
|
||||
#define WINSERVER_RESPONSE_SOCKET_PATH "/tmp/winserver_response.sock"
|
||||
#define WINSERVER_RESPONSE_SOCKET_PATH_SIZE sizeof(WINSERVER_RESPONSE_SOCKET_PATH)
|
||||
|
||||
namespace WinServer {
|
||||
|
||||
Connection* s_WinServer_Connection_the = nullptr;
|
||||
|
||||
Connection::Connection()
|
||||
: m_connection(LIPC::DoubleSidedConnection(socket(PF_LOCAL, 0, 0), socket(PF_LOCAL, 0, 0)))
|
||||
, m_server_decoder()
|
||||
, m_client_decoder()
|
||||
, m_connection_with_clients(m_connection, m_server_decoder, m_client_decoder)
|
||||
{
|
||||
s_WinServer_Connection_the = this;
|
||||
int err1 = bind(m_connection.c2s_fd(), WINSERVER_REQUEST_SOCKET_PATH, WINSERVER_REQUEST_SOCKET_PATH_SIZE);
|
||||
int err2 = bind(m_connection.s2c_fd(), WINSERVER_RESPONSE_SOCKET_PATH, WINSERVER_RESPONSE_SOCKET_PATH_SIZE);
|
||||
|
||||
if (!err1 && !err2) {
|
||||
LFoundation::EventLoop::the().add(
|
||||
m_connection.c2s_fd(), [] {
|
||||
Connection::the().listen();
|
||||
},
|
||||
nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
void Connection::receive_event(std::unique_ptr<LFoundation::Event> event)
|
||||
{
|
||||
switch (event->type()) {
|
||||
case WinServer::Event::Type::SendEvent: {
|
||||
std::unique_ptr<SendEvent> send_event = std::move(event);
|
||||
m_connection_with_clients.send_message(*send_event->message());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace WinServer
|
||||
37
userland/servers/window_server/src/IPC/Connection.h
Normal file
37
userland/servers/window_server/src/IPC/Connection.h
Normal file
@@ -0,0 +1,37 @@
|
||||
#pragma once
|
||||
#include "ServerDecoder.h"
|
||||
#include <libapi/window_server/Connections/WSConnection.h>
|
||||
#include <libfoundation/EventReceiver.h>
|
||||
#include <libipc/DoubleSidedConnection.h>
|
||||
#include <libipc/ServerConnection.h>
|
||||
|
||||
namespace WinServer {
|
||||
|
||||
class Connection : public LFoundation::EventReceiver {
|
||||
public:
|
||||
inline static Connection& the()
|
||||
{
|
||||
extern Connection* s_WinServer_Connection_the;
|
||||
return *s_WinServer_Connection_the;
|
||||
}
|
||||
|
||||
Connection();
|
||||
|
||||
inline void listen()
|
||||
{
|
||||
m_connection_with_clients.pump_messages();
|
||||
}
|
||||
|
||||
inline bool send_async_message(const Message& msg) const { return m_connection_with_clients.send_message(msg); }
|
||||
inline int alloc_connection() { return ++m_connections_number; }
|
||||
void receive_event(std::unique_ptr<LFoundation::Event> event) override;
|
||||
|
||||
private:
|
||||
LIPC::DoubleSidedConnection m_connection;
|
||||
int m_connections_number { 0 };
|
||||
ServerConnection<WindowServerDecoder, BaseWindowClientDecoder> m_connection_with_clients;
|
||||
WindowServerDecoder m_server_decoder;
|
||||
BaseWindowClientDecoder m_client_decoder;
|
||||
};
|
||||
|
||||
} // namespace WinServer
|
||||
102
userland/servers/window_server/src/IPC/Event.h
Normal file
102
userland/servers/window_server/src/IPC/Event.h
Normal file
@@ -0,0 +1,102 @@
|
||||
#pragma once
|
||||
|
||||
#include <libfoundation/Event.h>
|
||||
#include <libipc/Message.h>
|
||||
#include <memory>
|
||||
#include <sys/types.h>
|
||||
|
||||
namespace WinServer {
|
||||
|
||||
class Event : public LFoundation::Event {
|
||||
public:
|
||||
enum Type {
|
||||
Invalid = 0x1000,
|
||||
MouseEvent,
|
||||
KeyboardEvent,
|
||||
SendEvent,
|
||||
Other,
|
||||
};
|
||||
|
||||
explicit Event(int type)
|
||||
: LFoundation::Event(type)
|
||||
{
|
||||
}
|
||||
|
||||
~Event() = default;
|
||||
};
|
||||
|
||||
struct MousePacket {
|
||||
int16_t x_offset;
|
||||
int16_t y_offset;
|
||||
uint16_t button_states;
|
||||
int16_t wheel_data;
|
||||
};
|
||||
|
||||
struct KeyboardPacket {
|
||||
uint32_t key;
|
||||
};
|
||||
|
||||
class MouseEvent : public WinServer::Event {
|
||||
public:
|
||||
explicit MouseEvent(const MousePacket& packet)
|
||||
: WinServer::Event(WinServer::Event::Type::MouseEvent)
|
||||
, m_packet(packet)
|
||||
{
|
||||
}
|
||||
|
||||
~MouseEvent() = default;
|
||||
|
||||
const MousePacket& packet() const { return m_packet; }
|
||||
MousePacket& packet() { return m_packet; }
|
||||
|
||||
private:
|
||||
MousePacket m_packet;
|
||||
};
|
||||
|
||||
class KeyboardEvent : public WinServer::Event {
|
||||
public:
|
||||
explicit KeyboardEvent(const KeyboardPacket& packet)
|
||||
: WinServer::Event(WinServer::Event::Type::KeyboardEvent)
|
||||
, m_packet(packet)
|
||||
{
|
||||
}
|
||||
|
||||
~KeyboardEvent() = default;
|
||||
|
||||
const KeyboardPacket& packet() const { return m_packet; }
|
||||
KeyboardPacket& packet() { return m_packet; }
|
||||
|
||||
private:
|
||||
KeyboardPacket m_packet;
|
||||
};
|
||||
|
||||
class SendEvent : public WinServer::Event {
|
||||
public:
|
||||
explicit SendEvent(Message* msg)
|
||||
: WinServer::Event(WinServer::Event::Type::SendEvent)
|
||||
, m_message(msg)
|
||||
{
|
||||
}
|
||||
|
||||
SendEvent(SendEvent&& ev)
|
||||
: WinServer::Event(WinServer::Event::Type::SendEvent)
|
||||
, m_message(std::move(ev.m_message))
|
||||
{
|
||||
}
|
||||
|
||||
SendEvent& operator=(SendEvent&& ev)
|
||||
{
|
||||
m_message = std::move(ev.m_message);
|
||||
return *this;
|
||||
}
|
||||
|
||||
~SendEvent() = default;
|
||||
|
||||
const std::unique_ptr<Message>& message() const { return m_message; }
|
||||
std::unique_ptr<Message>& message() { return m_message; }
|
||||
|
||||
private:
|
||||
std::unique_ptr<Message> m_message;
|
||||
};
|
||||
|
||||
} // namespace WinServer
|
||||
220
userland/servers/window_server/src/IPC/ServerDecoder.cpp
Normal file
220
userland/servers/window_server/src/IPC/ServerDecoder.cpp
Normal file
@@ -0,0 +1,220 @@
|
||||
#include "ServerDecoder.h"
|
||||
#include "../Components/Security/Violations.h"
|
||||
#include "../Managers/WindowManager.h"
|
||||
#include "../Target/Generic/Window.h"
|
||||
|
||||
namespace WinServer {
|
||||
|
||||
std::unique_ptr<Message> WindowServerDecoder::handle(GreetMessage& msg)
|
||||
{
|
||||
return new GreetMessageReply(msg.key(), Connection::the().alloc_connection());
|
||||
}
|
||||
|
||||
#ifdef TARGET_DESKTOP
|
||||
std::unique_ptr<Message> WindowServerDecoder::handle(CreateWindowMessage& msg)
|
||||
{
|
||||
auto& wm = WindowManager::the();
|
||||
auto& compositor = Compositor::the();
|
||||
int win_id = wm.next_win_id();
|
||||
auto* window = new Desktop::Window(msg.key(), win_id, msg);
|
||||
window->set_app_title(msg.title().move_string());
|
||||
window->set_icon_path(msg.icon_path().move_string());
|
||||
window->set_style(StatusBarStyle(msg.menubar_style(), msg.color()));
|
||||
wm.add_window(window);
|
||||
if (window->type() == WindowType::Standard) {
|
||||
// After moving windows, we have to invalidate bounds() to make sure that
|
||||
// the whole window is rendered (coords are changes after move).
|
||||
wm.move_window(window, 8 * win_id, MenuBar::height() + 8 * win_id);
|
||||
compositor.invalidate(window->bounds());
|
||||
}
|
||||
|
||||
wm.notify_window_icon_changed(window->id());
|
||||
wm.notify_window_title_changed(window->id());
|
||||
return new CreateWindowMessageReply(msg.key(), win_id);
|
||||
}
|
||||
#elif TARGET_MOBILE
|
||||
std::unique_ptr<Message> WindowServerDecoder::handle(CreateWindowMessage& msg)
|
||||
{
|
||||
auto& wm = WindowManager::the();
|
||||
int win_id = wm.next_win_id();
|
||||
auto* window = new Mobile::Window(msg.key(), win_id, msg);
|
||||
window->set_style(StatusBarStyle(msg.menubar_style(), msg.color()));
|
||||
window->set_icon_path(msg.icon_path().move_string());
|
||||
window->set_style(StatusBarStyle(msg.menubar_style(), msg.color()));
|
||||
wm.add_window(window);
|
||||
wm.move_window(window, 0, MenuBar::height());
|
||||
wm.notify_window_icon_changed(window->id());
|
||||
wm.notify_window_title_changed(window->id());
|
||||
return new CreateWindowMessageReply(msg.key(), win_id);
|
||||
}
|
||||
#endif
|
||||
|
||||
std::unique_ptr<Message> WindowServerDecoder::handle(SetBufferMessage& msg)
|
||||
{
|
||||
auto* window = WindowManager::the().window(msg.window_id());
|
||||
if (!window) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
LG::Size new_size = { msg.bounds().width(), msg.bounds().height() };
|
||||
window->did_size_change(new_size);
|
||||
window->set_buffer(msg.buffer_id(), new_size, LG::PixelBitmapFormat(msg.format()));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::unique_ptr<Message> WindowServerDecoder::handle(DestroyWindowMessage& msg)
|
||||
{
|
||||
auto& wm = WindowManager::the();
|
||||
auto* window = wm.window(msg.window_id());
|
||||
if (!window) {
|
||||
wm.on_window_misbehave(*window, ViolationClass::Ignorable);
|
||||
return new DestroyWindowMessageReply(msg.key(), 1);
|
||||
}
|
||||
|
||||
if (window->connection_id() != msg.key()) {
|
||||
wm.on_window_misbehave(*window, ViolationClass::Serious);
|
||||
return new DestroyWindowMessageReply(msg.key(), 1);
|
||||
}
|
||||
wm.remove_window(window);
|
||||
return new DestroyWindowMessageReply(msg.key(), 0);
|
||||
}
|
||||
|
||||
std::unique_ptr<Message> WindowServerDecoder::handle(InvalidateMessage& msg)
|
||||
{
|
||||
auto& wm = WindowManager::the();
|
||||
auto* window = wm.window(msg.window_id());
|
||||
if (!window) {
|
||||
return nullptr;
|
||||
}
|
||||
auto rect = msg.rect();
|
||||
rect.offset_by(window->content_bounds().origin());
|
||||
rect.intersect(window->content_bounds());
|
||||
Compositor::the().invalidate(rect);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
#ifdef TARGET_DESKTOP
|
||||
std::unique_ptr<Message> WindowServerDecoder::handle(SetTitleMessage& msg)
|
||||
{
|
||||
auto& wm = WindowManager::the();
|
||||
auto* window = wm.window(msg.window_id());
|
||||
if (!window) {
|
||||
return nullptr;
|
||||
}
|
||||
window->set_app_title(msg.title().string());
|
||||
|
||||
auto& compositor = Compositor::the();
|
||||
compositor.invalidate(compositor.menu_bar().bounds());
|
||||
wm.notify_window_title_changed(window->id());
|
||||
return nullptr;
|
||||
}
|
||||
#elif TARGET_MOBILE
|
||||
std::unique_ptr<Message> WindowServerDecoder::handle(SetTitleMessage& msg)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
#endif
|
||||
|
||||
std::unique_ptr<Message> WindowServerDecoder::handle(SetBarStyleMessage& msg)
|
||||
{
|
||||
auto& wm = WindowManager::the();
|
||||
auto* window = wm.window(msg.window_id());
|
||||
if (!window) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
window->set_style(StatusBarStyle(msg.menubar_style(), msg.color()));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
#ifdef TARGET_DESKTOP
|
||||
std::unique_ptr<Message> WindowServerDecoder::handle(MenuBarCreateMenuMessage& msg)
|
||||
{
|
||||
auto& wm = WindowManager::the();
|
||||
auto* window = wm.window(msg.window_id());
|
||||
if (!window) {
|
||||
return new MenuBarCreateMenuMessageReply(msg.key(), -1, 0);
|
||||
}
|
||||
|
||||
int id = window->menubar_content().size();
|
||||
window->menubar_content().push_back(MenuDir(msg.title().string(), id));
|
||||
window->on_menubar_change();
|
||||
return new MenuBarCreateMenuMessageReply(msg.key(), 0, id);
|
||||
}
|
||||
|
||||
std::unique_ptr<Message> WindowServerDecoder::handle(MenuBarCreateItemMessage& msg)
|
||||
{
|
||||
auto& wm = WindowManager::the();
|
||||
auto* window = wm.window(msg.window_id());
|
||||
if (!window) {
|
||||
return new MenuBarCreateItemMessageReply(msg.key(), -1);
|
||||
}
|
||||
|
||||
auto menu_id = msg.menu_id();
|
||||
if (menu_id == 0 || window->menubar_content().size() <= menu_id) {
|
||||
return new MenuBarCreateItemMessageReply(msg.key(), -2);
|
||||
}
|
||||
|
||||
auto callback = [window, menu_id](int item_id) { LFoundation::EventLoop::the().add(Connection::the(),
|
||||
new SendEvent(new MenuBarActionMessage(window->connection_id(), window->id(), menu_id, item_id))); };
|
||||
window->menubar_content()[menu_id].add_item(PopupItem { msg.item_id(), msg.title().string(), callback });
|
||||
// TODO: Currently we don't redraw popup after a new item was added.
|
||||
return new MenuBarCreateItemMessageReply(msg.key(), 0);
|
||||
}
|
||||
#elif TARGET_MOBILE
|
||||
std::unique_ptr<Message> WindowServerDecoder::handle(MenuBarCreateMenuMessage& msg)
|
||||
{
|
||||
return new MenuBarCreateMenuMessageReply(msg.key(), -100, 0);
|
||||
}
|
||||
|
||||
std::unique_ptr<Message> WindowServerDecoder::handle(MenuBarCreateItemMessage& msg)
|
||||
{
|
||||
return new MenuBarCreateItemMessageReply(msg.key(), -100);
|
||||
}
|
||||
#endif
|
||||
|
||||
std::unique_ptr<Message> WindowServerDecoder::handle(PopupShowMenuMessage& msg)
|
||||
{
|
||||
auto& wm = WindowManager::the();
|
||||
auto* window = wm.window(msg.window_id());
|
||||
|
||||
if (!window) {
|
||||
return new PopupShowMenuMessageReply(msg.key(), -1, -1);
|
||||
}
|
||||
|
||||
std::vector<std::string> res;
|
||||
for (int i = 0; i < msg.data().vector().size(); i++) {
|
||||
res.push_back(msg.data().vector()[i].move_string());
|
||||
}
|
||||
|
||||
PopupData popup_data;
|
||||
int item_id = 0;
|
||||
int menu_id = 0;
|
||||
|
||||
for (auto& title : res) {
|
||||
auto callback = [window, menu_id](int item_id) { LFoundation::EventLoop::the().add(Connection::the(),
|
||||
new SendEvent(new PopupActionMessage(window->connection_id(), window->id(), menu_id, item_id))); };
|
||||
popup_data.push_back(PopupItem { item_id, title, callback });
|
||||
item_id++;
|
||||
}
|
||||
|
||||
wm.popup().show({ msg.point() }, popup_data);
|
||||
return new PopupShowMenuMessageReply(msg.key(), 0, 0);
|
||||
}
|
||||
|
||||
std::unique_ptr<Message> WindowServerDecoder::handle(AskBringToFrontMessage& msg)
|
||||
{
|
||||
auto& wm = WindowManager::the();
|
||||
auto* window = wm.window(msg.window_id());
|
||||
auto* target_window = wm.window(msg.target_window_id());
|
||||
if (!window || !target_window) {
|
||||
return nullptr;
|
||||
}
|
||||
if (window->type() == WindowType::Homescreen) {
|
||||
// Only dock can ask for that now.
|
||||
wm.ask_to_set_active_window(*target_window);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
} // namespace WinServer
|
||||
26
userland/servers/window_server/src/IPC/ServerDecoder.h
Normal file
26
userland/servers/window_server/src/IPC/ServerDecoder.h
Normal file
@@ -0,0 +1,26 @@
|
||||
#pragma once
|
||||
#include "../Managers/Compositor.h"
|
||||
#include <libapi/window_server/Connections/WSConnection.h>
|
||||
|
||||
namespace WinServer {
|
||||
|
||||
class WindowServerDecoder : public BaseWindowServerDecoder {
|
||||
public:
|
||||
WindowServerDecoder() = default;
|
||||
~WindowServerDecoder() = default;
|
||||
|
||||
using BaseWindowServerDecoder::handle;
|
||||
virtual std::unique_ptr<Message> handle(GreetMessage& msg) override;
|
||||
virtual std::unique_ptr<Message> handle(CreateWindowMessage& msg) override;
|
||||
virtual std::unique_ptr<Message> handle(DestroyWindowMessage& msg) override;
|
||||
virtual std::unique_ptr<Message> handle(SetBarStyleMessage& msg) override;
|
||||
virtual std::unique_ptr<Message> handle(SetTitleMessage& msg) override;
|
||||
virtual std::unique_ptr<Message> handle(SetBufferMessage& msg) override;
|
||||
virtual std::unique_ptr<Message> handle(InvalidateMessage& msg) override;
|
||||
virtual std::unique_ptr<Message> handle(MenuBarCreateMenuMessage& msg) override;
|
||||
virtual std::unique_ptr<Message> handle(MenuBarCreateItemMessage& msg) override;
|
||||
virtual std::unique_ptr<Message> handle(PopupShowMenuMessage& msg) override;
|
||||
virtual std::unique_ptr<Message> handle(AskBringToFrontMessage& msg) override;
|
||||
};
|
||||
|
||||
} // namespace WinServer
|
||||
159
userland/servers/window_server/src/Managers/Compositor.cpp
Normal file
159
userland/servers/window_server/src/Managers/Compositor.cpp
Normal file
@@ -0,0 +1,159 @@
|
||||
#include "Compositor.h"
|
||||
#include "../Components/Base/BaseWindow.h"
|
||||
#include "../Components/ControlBar/ControlBar.h"
|
||||
#include "../Components/MenuBar/MenuBar.h"
|
||||
#include "../Components/Popup/Popup.h"
|
||||
#include "../Devices/Screen.h"
|
||||
#include "../Managers/CursorManager.h"
|
||||
#include "../Managers/ResourceManager.h"
|
||||
#include "../Managers/WindowManager.h"
|
||||
#include <libfoundation/EventLoop.h>
|
||||
#include <libfoundation/Memory.h>
|
||||
#include <libg/Context.h>
|
||||
|
||||
namespace WinServer {
|
||||
|
||||
Compositor* s_WinServer_Compositor_the = nullptr;
|
||||
|
||||
Compositor::Compositor()
|
||||
: m_cursor_manager(CursorManager::the())
|
||||
, m_resource_manager(ResourceManager::the())
|
||||
, m_popup(Popup::the())
|
||||
, m_menu_bar(MenuBar::the())
|
||||
#ifdef TARGET_MOBILE
|
||||
, m_control_bar(ControlBar::the())
|
||||
#endif // TARGET_MOBILE
|
||||
{
|
||||
s_WinServer_Compositor_the = this;
|
||||
invalidate(Screen::the().bounds());
|
||||
LFoundation::EventLoop::the().add(LFoundation::Timer([] {
|
||||
Compositor::the().refresh();
|
||||
},
|
||||
1000 / 60, LFoundation::Timer::Repeat));
|
||||
}
|
||||
|
||||
void Compositor::copy_changes_to_second_buffer(const std::vector<LG::Rect>& areas)
|
||||
{
|
||||
auto& screen = Screen::the();
|
||||
|
||||
for (int i = 0; i < areas.size(); i++) {
|
||||
auto bounds = areas[i].intersection(screen.bounds());
|
||||
auto* buf1_ptr = reinterpret_cast<uint32_t*>(&screen.display_bitmap()[bounds.min_y()][bounds.min_x()]);
|
||||
auto* buf2_ptr = reinterpret_cast<uint32_t*>(&screen.write_bitmap()[bounds.min_y()][bounds.min_x()]);
|
||||
for (int j = 0; j < bounds.height(); j++) {
|
||||
LFoundation::fast_copy(buf2_ptr, buf1_ptr, bounds.width());
|
||||
buf1_ptr += screen.width();
|
||||
buf2_ptr += screen.width();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[[gnu::flatten]] void Compositor::refresh()
|
||||
{
|
||||
if (m_invalidated_areas.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto& screen = Screen::the();
|
||||
auto& wm = WindowManager::the();
|
||||
auto invalidated_areas = std::move(m_invalidated_areas);
|
||||
LG::Context ctx(screen.write_bitmap());
|
||||
|
||||
auto is_window_area_invalidated = [&](const std::vector<LG::Rect>& areas, const LG::Rect& area) -> bool {
|
||||
for (int i = 0; i < areas.size(); i++) {
|
||||
if (area.intersects(areas[i])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
auto draw_wallpaper_for_area = [&](const LG::Rect& area) {
|
||||
ctx.add_clip(area);
|
||||
ctx.draw({ 0, 0 }, m_resource_manager.background());
|
||||
ctx.reset_clip();
|
||||
};
|
||||
|
||||
#ifdef TARGET_DESKTOP
|
||||
auto draw_window = [&](Desktop::Window& window, const LG::Rect& area) {
|
||||
ctx.add_clip(area);
|
||||
ctx.add_clip(window.bounds());
|
||||
window.frame().draw(ctx);
|
||||
ctx.draw_rounded(window.content_bounds().origin(), window.content_bitmap(), window.corner_mask());
|
||||
ctx.reset_clip();
|
||||
};
|
||||
#elif TARGET_MOBILE
|
||||
auto draw_window = [&](Mobile::Window& window, const LG::Rect& area) {
|
||||
ctx.add_clip(area);
|
||||
ctx.add_clip(window.bounds());
|
||||
ctx.draw(window.content_bounds().origin(), window.content_bitmap());
|
||||
ctx.reset_clip();
|
||||
};
|
||||
#endif // TARGET_DESKTOP
|
||||
|
||||
auto& windows = wm.windows();
|
||||
auto top_std_window = windows.rbegin();
|
||||
#ifdef TARGET_DESKTOP
|
||||
for (int i = 0; i < invalidated_areas.size(); i++) {
|
||||
draw_wallpaper_for_area(invalidated_areas[i]);
|
||||
}
|
||||
#elif TARGET_MOBILE
|
||||
// Standard windows could not be transparent in mobile view, thus
|
||||
// no need to render everything behind the first standard window.
|
||||
for (auto it = windows.rbegin(); it != windows.rend(); it++) {
|
||||
top_std_window = it;
|
||||
}
|
||||
|
||||
if (top_std_window == windows.rend() || (*top_std_window)->type() != WindowType::Standard) {
|
||||
for (int i = 0; i < invalidated_areas.size(); i++) {
|
||||
draw_wallpaper_for_area(invalidated_areas[i]);
|
||||
}
|
||||
}
|
||||
#endif // TARGET_DESKTOP
|
||||
|
||||
for (auto it = top_std_window; it != windows.rend(); it++) {
|
||||
auto& window = *(*it);
|
||||
if (window.visible() && is_window_area_invalidated(invalidated_areas, window.bounds())) {
|
||||
for (int i = 0; i < invalidated_areas.size(); i++) {
|
||||
draw_window(window, invalidated_areas[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (m_popup.visible()) {
|
||||
for (int i = 0; i < invalidated_areas.size(); i++) {
|
||||
ctx.add_clip(invalidated_areas[i]);
|
||||
m_popup.draw(ctx);
|
||||
ctx.reset_clip();
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < invalidated_areas.size(); i++) {
|
||||
if (m_menu_bar.bounds().intersects(invalidated_areas[i])) {
|
||||
ctx.add_clip(invalidated_areas[i]);
|
||||
m_menu_bar.draw(ctx);
|
||||
ctx.reset_clip();
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef TARGET_MOBILE
|
||||
for (int i = 0; i < invalidated_areas.size(); i++) {
|
||||
ctx.add_clip(invalidated_areas[i]);
|
||||
m_control_bar.draw(ctx);
|
||||
ctx.reset_clip();
|
||||
}
|
||||
#endif // TARGET_MOBILE
|
||||
|
||||
auto mouse_draw_position = m_cursor_manager.draw_position();
|
||||
auto& current_mouse_bitmap = m_cursor_manager.current_cursor();
|
||||
for (int i = 0; i < invalidated_areas.size(); i++) {
|
||||
ctx.add_clip(invalidated_areas[i]);
|
||||
ctx.draw(mouse_draw_position, current_mouse_bitmap);
|
||||
ctx.reset_clip();
|
||||
}
|
||||
|
||||
screen.swap_buffers();
|
||||
copy_changes_to_second_buffer(invalidated_areas);
|
||||
}
|
||||
|
||||
} // namespace WinServer
|
||||
95
userland/servers/window_server/src/Managers/Compositor.h
Normal file
95
userland/servers/window_server/src/Managers/Compositor.h
Normal file
@@ -0,0 +1,95 @@
|
||||
#pragma once
|
||||
#include "../IPC/ServerDecoder.h"
|
||||
#include <libapi/window_server/Connections/WSConnection.h>
|
||||
#include <libipc/ServerConnection.h>
|
||||
#include <vector>
|
||||
|
||||
namespace WinServer {
|
||||
|
||||
class CursorManager;
|
||||
class ResourceManager;
|
||||
class MenuBar;
|
||||
#ifdef TARGET_MOBILE
|
||||
class ControlBar;
|
||||
#endif // TARGET_MOBILE
|
||||
class Popup;
|
||||
|
||||
class Compositor {
|
||||
public:
|
||||
inline static Compositor& the()
|
||||
{
|
||||
extern Compositor* s_WinServer_Compositor_the;
|
||||
return *s_WinServer_Compositor_the;
|
||||
}
|
||||
|
||||
Compositor();
|
||||
|
||||
void refresh();
|
||||
|
||||
void optimized_invalidate_insert(std::vector<LG::Rect>& data, const LG::Rect& inv_area)
|
||||
{
|
||||
auto area = inv_area;
|
||||
bool intersects = false;
|
||||
int int_index = 0;
|
||||
for (auto& rect : data) {
|
||||
if (rect.contains(area)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
for (auto& rect : data) {
|
||||
if (rect.intersects(area)) {
|
||||
intersects = true;
|
||||
break;
|
||||
}
|
||||
int_index++;
|
||||
}
|
||||
|
||||
if (!intersects) {
|
||||
data.push_back(area);
|
||||
return;
|
||||
}
|
||||
|
||||
data[int_index].unite(area);
|
||||
for (int i = int_index + 1; i < data.size(); i++) {
|
||||
if (data[int_index].intersects(data[i])) {
|
||||
data[int_index].unite(data[i]);
|
||||
std::swap(data[i], data.back());
|
||||
data.pop_back();
|
||||
i--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline void invalidate(const LG::Rect& area) { optimized_invalidate_insert(m_invalidated_areas, area); }
|
||||
inline CursorManager& cursor_manager() { return m_cursor_manager; }
|
||||
inline const CursorManager& cursor_manager() const { return m_cursor_manager; }
|
||||
inline ResourceManager& resource_manager() { return m_resource_manager; }
|
||||
inline const ResourceManager& resource_manager() const { return m_resource_manager; }
|
||||
inline Popup& popup() { return m_popup; }
|
||||
inline const Popup& popup() const { return m_popup; }
|
||||
inline MenuBar& menu_bar() { return m_menu_bar; }
|
||||
inline const MenuBar& menu_bar() const { return m_menu_bar; }
|
||||
#ifdef TARGET_MOBILE
|
||||
inline ControlBar& control_bar()
|
||||
{
|
||||
return m_control_bar;
|
||||
}
|
||||
inline const ControlBar& control_bar() const { return m_control_bar; }
|
||||
#endif // TARGET_MOBILE
|
||||
|
||||
private:
|
||||
void copy_changes_to_second_buffer(const std::vector<LG::Rect>& areas);
|
||||
|
||||
std::vector<LG::Rect> m_invalidated_areas;
|
||||
MenuBar& m_menu_bar;
|
||||
Popup& m_popup;
|
||||
CursorManager& m_cursor_manager;
|
||||
ResourceManager& m_resource_manager;
|
||||
|
||||
#ifdef TARGET_MOBILE
|
||||
ControlBar& m_control_bar;
|
||||
#endif // TARGET_MOBILE
|
||||
};
|
||||
|
||||
} // namespace WinServer
|
||||
@@ -0,0 +1,22 @@
|
||||
#include "CursorManager.h"
|
||||
#include <libg/ImageLoaders/PNGLoader.h>
|
||||
|
||||
#ifdef TARGET_DESKTOP
|
||||
#define CURSOR_PATH "/res/system/arrow.png"
|
||||
#elif TARGET_MOBILE
|
||||
#define CURSOR_PATH "/res/system/mobile/cursor.png"
|
||||
#endif
|
||||
|
||||
namespace WinServer {
|
||||
|
||||
CursorManager* s_WinServer_CursorManager_the = nullptr;
|
||||
|
||||
CursorManager::CursorManager()
|
||||
: m_screen(Screen::the())
|
||||
{
|
||||
s_WinServer_CursorManager_the = this;
|
||||
LG::PNG::PNGLoader loader;
|
||||
m_std_cursor = loader.load_from_file(CURSOR_PATH);
|
||||
}
|
||||
|
||||
} // namespace WinServer
|
||||
213
userland/servers/window_server/src/Managers/CursorManager.h
Normal file
213
userland/servers/window_server/src/Managers/CursorManager.h
Normal file
@@ -0,0 +1,213 @@
|
||||
#pragma once
|
||||
#include "../Devices/Screen.h"
|
||||
#include "../IPC/Event.h"
|
||||
#include <algorithm>
|
||||
#include <libg/PixelBitmap.h>
|
||||
#include <libg/Point.h>
|
||||
|
||||
#ifdef TARGET_DESKTOP
|
||||
#define CURSOR_OFFSET (2)
|
||||
#elif TARGET_MOBILE
|
||||
#define CURSOR_OFFSET (6)
|
||||
#endif
|
||||
|
||||
namespace WinServer {
|
||||
|
||||
class CursorManager {
|
||||
public:
|
||||
enum class Params {
|
||||
X,
|
||||
Y,
|
||||
LeftButton,
|
||||
RightButton,
|
||||
OffsetX,
|
||||
OffsetY,
|
||||
Wheel,
|
||||
|
||||
// Only for getters of conditions changes.
|
||||
Coords,
|
||||
Buttons,
|
||||
};
|
||||
|
||||
inline static CursorManager& the()
|
||||
{
|
||||
extern CursorManager* s_WinServer_CursorManager_the;
|
||||
return *s_WinServer_CursorManager_the;
|
||||
}
|
||||
|
||||
CursorManager();
|
||||
~CursorManager() = default;
|
||||
|
||||
inline const LG::PixelBitmap& current_cursor() const { return std_cursor(); }
|
||||
inline const LG::PixelBitmap& std_cursor() const { return m_std_cursor; }
|
||||
inline LG::Point<int> draw_position() { return { m_mouse_x - CURSOR_OFFSET, m_mouse_y - CURSOR_OFFSET }; }
|
||||
|
||||
inline int x() const
|
||||
{
|
||||
return m_mouse_x;
|
||||
}
|
||||
inline int y() const { return m_mouse_y; }
|
||||
|
||||
template <Params param>
|
||||
constexpr int get();
|
||||
|
||||
template <Params param>
|
||||
constexpr bool pressed() const;
|
||||
|
||||
template <Params param>
|
||||
constexpr bool is_changed();
|
||||
inline void clear_changed(uint32_t val = 0) { m_mask_changed_objects = val; }
|
||||
|
||||
template <Params param, typename Value>
|
||||
inline constexpr void set(Value val)
|
||||
{
|
||||
if constexpr (param == CursorManager::Params::X) {
|
||||
val = std::max(std::min(val, (int)m_screen.width() - 1), 0);
|
||||
m_mouse_offset_x = val - m_mouse_x;
|
||||
m_mouse_x = val;
|
||||
set_changed<CursorManager::Params::X>();
|
||||
} else if constexpr (param == CursorManager::Params::Y) {
|
||||
val = std::max(std::min(val, (int)m_screen.height() - 1), 0);
|
||||
m_mouse_offset_y = val - m_mouse_y;
|
||||
m_mouse_y = val;
|
||||
set_changed<CursorManager::Params::Y>();
|
||||
} else if constexpr (param == CursorManager::Params::OffsetX) {
|
||||
if (val != 0) {
|
||||
set<CursorManager::Params::X>(m_mouse_x + val);
|
||||
} else {
|
||||
m_mouse_offset_x = 0;
|
||||
}
|
||||
} else if constexpr (param == CursorManager::Params::OffsetY) {
|
||||
if (val != 0) {
|
||||
set<CursorManager::Params::Y>(m_mouse_y + val);
|
||||
} else {
|
||||
m_mouse_offset_y = 0;
|
||||
}
|
||||
} else if constexpr (param == CursorManager::Params::LeftButton) {
|
||||
if (m_mouse_left_button_pressed != val) {
|
||||
set_changed<CursorManager::Params::LeftButton>();
|
||||
}
|
||||
m_mouse_left_button_pressed = val;
|
||||
} else if constexpr (param == CursorManager::Params::RightButton) {
|
||||
if (m_mouse_right_button_pressed != val) {
|
||||
set_changed<CursorManager::Params::RightButton>();
|
||||
}
|
||||
m_mouse_right_button_pressed = val;
|
||||
} else if constexpr (param == CursorManager::Params::Wheel) {
|
||||
if (val != 0) {
|
||||
set_changed<CursorManager::Params::Wheel>();
|
||||
}
|
||||
m_wheel = val;
|
||||
} else {
|
||||
[]<bool flag = false>() { static_assert(flag, "Could not call set() with such param!"); }
|
||||
();
|
||||
}
|
||||
}
|
||||
|
||||
template <Params param>
|
||||
inline constexpr void set_changed()
|
||||
{
|
||||
if constexpr (param == CursorManager::Params::X) {
|
||||
m_mask_changed_objects |= CursorManager::ChangedValues::MouseCoords;
|
||||
} else if constexpr (param == CursorManager::Params::Y) {
|
||||
m_mask_changed_objects |= CursorManager::ChangedValues::MouseCoords;
|
||||
} else if constexpr (param == CursorManager::Params::LeftButton) {
|
||||
m_mask_changed_objects |= CursorManager::ChangedValues::LeftButton;
|
||||
} else if constexpr (param == CursorManager::Params::RightButton) {
|
||||
m_mask_changed_objects |= CursorManager::ChangedValues::RightButton;
|
||||
} else if constexpr (param == CursorManager::Params::Wheel) {
|
||||
m_mask_changed_objects |= CursorManager::ChangedValues::Wheel;
|
||||
} else {
|
||||
[]<bool flag = false>() { static_assert(flag, "Could not set_changed() for the param!"); }
|
||||
();
|
||||
}
|
||||
}
|
||||
|
||||
void update_position(MouseEvent* mouse_event)
|
||||
{
|
||||
clear_changed();
|
||||
set<Params::OffsetX>(mouse_event->packet().x_offset);
|
||||
set<Params::OffsetY>(-mouse_event->packet().y_offset);
|
||||
set<Params::LeftButton>((mouse_event->packet().button_states & 1));
|
||||
set<Params::RightButton>((mouse_event->packet().button_states & 2) >> 1);
|
||||
set<Params::Wheel>(mouse_event->packet().wheel_data);
|
||||
}
|
||||
|
||||
private:
|
||||
enum ChangedValues {
|
||||
MouseCoords = 0x1,
|
||||
LeftButton = 0x2,
|
||||
RightButton = 0x4,
|
||||
Wheel = 0x8,
|
||||
};
|
||||
|
||||
int m_mouse_x { 0 };
|
||||
int m_mouse_y { 0 };
|
||||
int m_mouse_offset_x { 0 };
|
||||
int m_mouse_offset_y { 0 };
|
||||
int m_wheel { 0 };
|
||||
bool m_mouse_left_button_pressed { false };
|
||||
bool m_mouse_right_button_pressed { false };
|
||||
uint32_t m_mask_changed_objects { 0 };
|
||||
bool m_mouse_changed_button_status { false };
|
||||
|
||||
Screen& m_screen;
|
||||
LG::PixelBitmap m_std_cursor;
|
||||
};
|
||||
|
||||
template <CursorManager::Params param>
|
||||
inline constexpr int CursorManager::get()
|
||||
{
|
||||
if constexpr (param == CursorManager::Params::X) {
|
||||
return m_mouse_x;
|
||||
} else if constexpr (param == CursorManager::Params::Y) {
|
||||
return m_mouse_y;
|
||||
} else if constexpr (param == CursorManager::Params::OffsetX) {
|
||||
return m_mouse_offset_x;
|
||||
} else if constexpr (param == CursorManager::Params::OffsetY) {
|
||||
return m_mouse_offset_y;
|
||||
} else if constexpr (param == CursorManager::Params::Wheel) {
|
||||
return m_wheel;
|
||||
} else {
|
||||
[]<bool flag = false>() { static_assert(flag, "Could call get() only for coords-like params!"); }
|
||||
();
|
||||
}
|
||||
}
|
||||
|
||||
template <CursorManager::Params param>
|
||||
inline constexpr bool CursorManager::pressed() const
|
||||
{
|
||||
if constexpr (param == CursorManager::Params::LeftButton) {
|
||||
return m_mouse_left_button_pressed;
|
||||
} else if constexpr (param == CursorManager::Params::RightButton) {
|
||||
return m_mouse_right_button_pressed;
|
||||
} else {
|
||||
[]<bool flag = false>() { static_assert(flag, "Could call pressed() only for buttons!"); }
|
||||
();
|
||||
}
|
||||
}
|
||||
|
||||
template <CursorManager::Params param>
|
||||
inline constexpr bool CursorManager::is_changed()
|
||||
{
|
||||
if constexpr (param == CursorManager::Params::X) {
|
||||
return (m_mask_changed_objects & CursorManager::ChangedValues::MouseCoords);
|
||||
} else if constexpr (param == CursorManager::Params::Y) {
|
||||
return (m_mask_changed_objects & CursorManager::ChangedValues::MouseCoords);
|
||||
} else if constexpr (param == CursorManager::Params::LeftButton) {
|
||||
return (m_mask_changed_objects & CursorManager::ChangedValues::LeftButton);
|
||||
} else if constexpr (param == CursorManager::Params::RightButton) {
|
||||
return (m_mask_changed_objects & CursorManager::ChangedValues::RightButton);
|
||||
} else if constexpr (param == CursorManager::Params::Coords) {
|
||||
return (m_mask_changed_objects & CursorManager::ChangedValues::MouseCoords);
|
||||
} else if constexpr (param == CursorManager::Params::Buttons) {
|
||||
return (m_mask_changed_objects & CursorManager::ChangedValues::LeftButton) | (m_mask_changed_objects & CursorManager::ChangedValues::RightButton);
|
||||
} else if constexpr (param == CursorManager::Params::Wheel) {
|
||||
return (m_mask_changed_objects & CursorManager::ChangedValues::Wheel);
|
||||
} else {
|
||||
[]<bool flag = false>() { static_assert(flag, "Could not call is_changed() for the param!"); }
|
||||
();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace WinServer
|
||||
69
userland/servers/window_server/src/Managers/InitManager.h
Normal file
69
userland/servers/window_server/src/Managers/InitManager.h
Normal file
@@ -0,0 +1,69 @@
|
||||
#pragma once
|
||||
|
||||
#include "../Components/ControlBar/ControlBar.h"
|
||||
#include "../Components/LoadingScreen/LoadingScreen.h"
|
||||
#include "../Components/MenuBar/MenuBar.h"
|
||||
#include "../Components/MenuBar/Widgets/Clock/Clock.h"
|
||||
#include "../Components/MenuBar/Widgets/ControlPanelToggle/ControlPanelToggle.h"
|
||||
#include "../Components/Popup/Popup.h"
|
||||
#include "../Devices/Devices.h"
|
||||
#include "../Devices/Screen.h"
|
||||
#include "../IPC/Connection.h"
|
||||
#include "Compositor.h"
|
||||
#include "CursorManager.h"
|
||||
#include "ResourceManager.h"
|
||||
#include "WindowManager.h"
|
||||
#include <cstdlib>
|
||||
#include <libfoundation/EventLoop.h>
|
||||
#include <new>
|
||||
#include <sys/socket.h>
|
||||
#include <unistd.h>
|
||||
|
||||
namespace WinServer {
|
||||
|
||||
class InitManager {
|
||||
public:
|
||||
InitManager() = delete;
|
||||
~InitManager() = delete;
|
||||
|
||||
static void load_screen()
|
||||
{
|
||||
nice(-3);
|
||||
new WinServer::Screen();
|
||||
new WinServer::LoadingScreen();
|
||||
}
|
||||
|
||||
template <class T, int Cost = 1, class... Args>
|
||||
static constexpr inline void load_core_component(Args&&... args)
|
||||
{
|
||||
new T(std::forward<Args>(args)...);
|
||||
WinServer::LoadingScreen::the().move_progress<T, Cost>();
|
||||
}
|
||||
|
||||
template <class T, int Cost = 1, class... Args>
|
||||
static constexpr inline void add_widget(Args&&... args)
|
||||
{
|
||||
WinServer::MenuBar::the().add_widget<T>(std::forward<Args>(args)...);
|
||||
WinServer::LoadingScreen::the().move_progress<T, Cost>();
|
||||
}
|
||||
|
||||
static inline int launch_app(const char* path, int uid = 0)
|
||||
{
|
||||
int pid = fork();
|
||||
if (!pid) {
|
||||
setuid(uid);
|
||||
setgid(uid);
|
||||
for (int i = 3; i < 32; i++) {
|
||||
close(i);
|
||||
}
|
||||
|
||||
execlp(path, path, NULL);
|
||||
std::abort();
|
||||
}
|
||||
return pid;
|
||||
}
|
||||
|
||||
private:
|
||||
};
|
||||
|
||||
};
|
||||
@@ -0,0 +1,15 @@
|
||||
#include "ResourceManager.h"
|
||||
#include <libg/ImageLoaders/PNGLoader.h>
|
||||
|
||||
namespace WinServer {
|
||||
|
||||
ResourceManager* s_WinServer_ResourceManager_the = nullptr;
|
||||
|
||||
ResourceManager::ResourceManager()
|
||||
{
|
||||
s_WinServer_ResourceManager_the = this;
|
||||
LG::PNG::PNGLoader loader;
|
||||
m_background = loader.load_from_file("/res/wallpapers/abstract.png");
|
||||
}
|
||||
|
||||
} // namespace WinServer
|
||||
@@ -0,0 +1,23 @@
|
||||
#pragma once
|
||||
#include <libg/PixelBitmap.h>
|
||||
#include <libg/Point.h>
|
||||
|
||||
namespace WinServer {
|
||||
|
||||
class ResourceManager {
|
||||
public:
|
||||
inline static ResourceManager& the()
|
||||
{
|
||||
extern ResourceManager* s_WinServer_ResourceManager_the;
|
||||
return *s_WinServer_ResourceManager_the;
|
||||
}
|
||||
|
||||
ResourceManager();
|
||||
|
||||
inline const LG::PixelBitmap& background() const { return m_background; }
|
||||
|
||||
private:
|
||||
LG::PixelBitmap m_background;
|
||||
};
|
||||
|
||||
} // namespace WinServer
|
||||
599
userland/servers/window_server/src/Managers/WindowManager.cpp
Normal file
599
userland/servers/window_server/src/Managers/WindowManager.cpp
Normal file
@@ -0,0 +1,599 @@
|
||||
#include "WindowManager.h"
|
||||
#include "../Devices/Screen.h"
|
||||
#include "../Managers/CursorManager.h"
|
||||
#include <libapi/window_server/MessageContent/MouseAction.h>
|
||||
#include <libfoundation/KeyboardMapping.h>
|
||||
#include <libfoundation/Logger.h>
|
||||
|
||||
// #define WM_DEBUG
|
||||
|
||||
namespace WinServer {
|
||||
|
||||
static PopupData WindowPopupData {};
|
||||
|
||||
WindowManager* s_WinServer_WindowManager_the = nullptr;
|
||||
|
||||
WindowManager::WindowManager()
|
||||
: m_screen(Screen::the())
|
||||
, m_connection(Connection::the())
|
||||
, m_compositor(Compositor::the())
|
||||
, m_cursor_manager(CursorManager::the())
|
||||
, m_event_loop(LFoundation::EventLoop::the())
|
||||
, m_std_menubar_content()
|
||||
, m_visible_area(m_screen.bounds())
|
||||
{
|
||||
s_WinServer_WindowManager_the = this;
|
||||
shrink_visible_area(menu_bar().height(), 0);
|
||||
#ifdef TARGET_DESKTOP
|
||||
menu_bar().set_background_color(LG::Color::LightSystemOpaque128);
|
||||
#endif // TARGET_DESKTOP
|
||||
}
|
||||
|
||||
void WindowManager::setup_dock(Window* window)
|
||||
{
|
||||
#ifdef TARGET_DESKTOP
|
||||
window->make_frameless();
|
||||
window->bounds().set_y(m_screen.bounds().max_y() - window->bounds().height() + 1);
|
||||
window->content_bounds().set_y(m_screen.bounds().max_y() - window->bounds().height() + 1);
|
||||
shrink_visible_area(0, window->bounds().height());
|
||||
#endif // TARGET_DESKTOP
|
||||
window->set_event_mask(WindowEvent::IconChange | WindowEvent::WindowStatus | WindowEvent::WindowCreation | WindowEvent::TitleChange);
|
||||
m_dock.set_window(window);
|
||||
}
|
||||
|
||||
void WindowManager::setup_applist(Window* window)
|
||||
{
|
||||
#ifdef TARGET_DESKTOP
|
||||
window->make_frameless();
|
||||
const size_t coorx = (visible_area().max_x() - window->bounds().width()) / 2;
|
||||
const size_t coory = visible_area().max_y() - window->bounds().height() - 8;
|
||||
window->bounds().set_x(coorx);
|
||||
window->content_bounds().set_x(coorx);
|
||||
window->bounds().set_y(coory);
|
||||
window->content_bounds().set_y(coory);
|
||||
#endif // TARGET_DESKTOP
|
||||
m_applist.set_window(window);
|
||||
minimize_window(*window);
|
||||
}
|
||||
|
||||
void WindowManager::add_system_window(Window* window)
|
||||
{
|
||||
switch (window->type()) {
|
||||
case WindowType::Homescreen:
|
||||
setup_dock(window);
|
||||
break;
|
||||
|
||||
case WindowType::AppList:
|
||||
setup_applist(window);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void WindowManager::add_window(Window* window)
|
||||
{
|
||||
m_windows.push_back(window);
|
||||
set_active_window(window);
|
||||
|
||||
if (window->type() != WindowType::Standard) {
|
||||
add_system_window(window);
|
||||
}
|
||||
notify_window_creation(window->id());
|
||||
}
|
||||
|
||||
void WindowManager::remove_attention_from_window(Window* window)
|
||||
{
|
||||
if (movable_window() == window) {
|
||||
m_movable_window = nullptr;
|
||||
}
|
||||
if (active_window() == window) {
|
||||
set_active_window(nullptr);
|
||||
}
|
||||
if (hovered_window() == window) {
|
||||
set_hovered_window(nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
void WindowManager::on_window_became_invisible(Window* window)
|
||||
{
|
||||
if (window->type() == WindowType::Standard && active_window() == window) {
|
||||
#ifdef TARGET_DESKTOP
|
||||
menu_bar().set_menubar_content(nullptr, m_compositor);
|
||||
#elif TARGET_MOBILE
|
||||
menu_bar().set_style(StatusBarStyle::StandardOpaque);
|
||||
m_compositor.invalidate(menu_bar().bounds());
|
||||
#endif
|
||||
}
|
||||
m_compositor.invalidate(window->bounds());
|
||||
}
|
||||
|
||||
void WindowManager::remove_window(Window* window_ptr)
|
||||
{
|
||||
notify_window_status_changed(window_ptr->id(), WindowStatusUpdateType::Removed);
|
||||
m_windows.erase(std::find(m_windows.begin(), m_windows.end(), window_ptr));
|
||||
on_window_became_invisible(window_ptr);
|
||||
remove_attention_from_window(window_ptr);
|
||||
delete window_ptr;
|
||||
}
|
||||
|
||||
void WindowManager::minimize_window(Window& window)
|
||||
{
|
||||
Window* window_ptr = &window;
|
||||
notify_window_status_changed(window.id(), WindowStatusUpdateType::Minimized);
|
||||
window.set_visible(false);
|
||||
m_windows.erase(std::find(m_windows.begin(), m_windows.end(), window_ptr));
|
||||
m_windows.push_back(window_ptr);
|
||||
on_window_became_invisible(window_ptr);
|
||||
remove_attention_from_window(window_ptr);
|
||||
}
|
||||
|
||||
void WindowManager::resize_window(Window& window, const LG::Size& size)
|
||||
{
|
||||
window.did_size_change(size);
|
||||
send_event(new ResizeMessage(window.connection_id(), window.id(), LG::Rect(0, 0, size.width(), size.height())));
|
||||
m_compositor.invalidate(window.bounds());
|
||||
}
|
||||
|
||||
void WindowManager::maximize_window(Window& window)
|
||||
{
|
||||
size_t fullscreen_h = m_screen.height();
|
||||
fullscreen_h = visible_area().height();
|
||||
|
||||
const size_t vertical_borders = Desktop::WindowFrame::std_top_border_size() + Desktop::WindowFrame::std_bottom_border_size();
|
||||
const size_t horizontal_borders = Desktop::WindowFrame::std_left_border_size() + Desktop::WindowFrame::std_right_border_size();
|
||||
|
||||
move_window(&window, -window.bounds().min_x(), -(window.bounds().min_y() - menu_bar().height()));
|
||||
resize_window(window, { m_screen.width() - horizontal_borders, fullscreen_h - vertical_borders });
|
||||
}
|
||||
|
||||
void WindowManager::start_window_move(Window& window)
|
||||
{
|
||||
m_movable_window = &window;
|
||||
}
|
||||
|
||||
WindowManager::Window* WindowManager::top_window_in_view(WindowType type) const
|
||||
{
|
||||
for (auto it = m_windows.begin(); it != m_windows.end(); it++) {
|
||||
auto* window = (*it);
|
||||
if (window->type() == type) {
|
||||
return window;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
#ifdef TARGET_DESKTOP
|
||||
void WindowManager::on_active_window_will_change()
|
||||
{
|
||||
if (!m_active_window) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_active_window->type() == WindowType::AppList) {
|
||||
m_active_window->set_visible(false);
|
||||
on_window_became_invisible(m_active_window);
|
||||
}
|
||||
}
|
||||
|
||||
void WindowManager::on_active_window_did_change()
|
||||
{
|
||||
}
|
||||
|
||||
void WindowManager::bring_system_windows_to_front()
|
||||
{
|
||||
if (m_dock.has_value()) {
|
||||
do_bring_to_front(*m_dock.window());
|
||||
}
|
||||
}
|
||||
|
||||
void WindowManager::bring_to_front(Window& window)
|
||||
{
|
||||
auto* prev_window = top_window_in_view(WindowType::Standard);
|
||||
do_bring_to_front(window);
|
||||
bring_system_windows_to_front();
|
||||
|
||||
window.set_visible(true);
|
||||
window.frame().set_active(true);
|
||||
m_compositor.invalidate(window.bounds());
|
||||
if (prev_window && prev_window->id() != window.id()) {
|
||||
prev_window->frame().set_active(false);
|
||||
prev_window->frame().invalidate(m_compositor);
|
||||
}
|
||||
if (window.type() == WindowType::Standard) {
|
||||
menu_bar().set_menubar_content(&window.menubar_content(), m_compositor);
|
||||
} else {
|
||||
menu_bar().set_menubar_content(nullptr, m_compositor);
|
||||
}
|
||||
}
|
||||
#elif TARGET_MOBILE
|
||||
void WindowManager::on_active_window_will_change()
|
||||
{
|
||||
}
|
||||
|
||||
void WindowManager::on_active_window_did_change()
|
||||
{
|
||||
// If current active_window has become NULL, try to restore the lastest.
|
||||
if (active_window() == nullptr) {
|
||||
if (auto top_window = m_windows.begin(); top_window != m_windows.end()) {
|
||||
m_active_window = *top_window;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void WindowManager::bring_system_windows_to_front()
|
||||
{
|
||||
}
|
||||
|
||||
void WindowManager::bring_to_front(Window& window)
|
||||
{
|
||||
do_bring_to_front(window);
|
||||
bring_system_windows_to_front();
|
||||
window.set_visible(true);
|
||||
m_active_window = &window;
|
||||
m_compositor.invalidate(window.bounds());
|
||||
if (window.type() == WindowType::Standard) {
|
||||
menu_bar().set_style(window.style());
|
||||
m_compositor.invalidate(menu_bar().bounds());
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef TARGET_DESKTOP
|
||||
bool WindowManager::continue_window_move()
|
||||
{
|
||||
if (!movable_window()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!m_cursor_manager.pressed<CursorManager::Params::LeftButton>()) {
|
||||
m_movable_window = nullptr;
|
||||
return true;
|
||||
}
|
||||
|
||||
auto bounds = movable_window()->bounds();
|
||||
m_compositor.invalidate(movable_window()->bounds());
|
||||
move_window(movable_window(), m_cursor_manager.get<CursorManager::Params::OffsetX>(), m_cursor_manager.get<CursorManager::Params::OffsetY>());
|
||||
bounds.unite(movable_window()->bounds());
|
||||
m_compositor.invalidate(bounds);
|
||||
return true;
|
||||
}
|
||||
#endif // TARGET_DESKTOP
|
||||
|
||||
void WindowManager::update_mouse_position(std::unique_ptr<LFoundation::Event> mouse_event)
|
||||
{
|
||||
auto invalidate_bounds = m_cursor_manager.current_cursor().bounds();
|
||||
invalidate_bounds.origin().set(m_cursor_manager.draw_position());
|
||||
m_compositor.invalidate(invalidate_bounds);
|
||||
|
||||
m_cursor_manager.update_position((WinServer::MouseEvent*)mouse_event.get());
|
||||
|
||||
invalidate_bounds.origin().set(m_cursor_manager.draw_position());
|
||||
m_compositor.invalidate(invalidate_bounds);
|
||||
}
|
||||
|
||||
#ifdef TARGET_DESKTOP
|
||||
void WindowManager::receive_mouse_event(std::unique_ptr<LFoundation::Event> event)
|
||||
{
|
||||
update_mouse_position(std::move(event));
|
||||
if (continue_window_move()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Checking and dispatching mouse move for Popup.
|
||||
if (popup().visible() && popup().bounds().contains(m_cursor_manager.x(), m_cursor_manager.y())) {
|
||||
popup().on_mouse_move(m_cursor_manager);
|
||||
if (m_cursor_manager.is_changed<CursorManager::Params::Buttons>()) {
|
||||
popup().on_mouse_status_change(m_cursor_manager);
|
||||
}
|
||||
return;
|
||||
} else {
|
||||
popup().on_mouse_leave(m_cursor_manager);
|
||||
if (m_cursor_manager.pressed<CursorManager::Params::LeftButton>()) {
|
||||
popup().set_visible(false);
|
||||
}
|
||||
}
|
||||
|
||||
// Checking and dispatching mouse move for MenuBar.
|
||||
if (menu_bar().bounds().contains(m_cursor_manager.x(), m_cursor_manager.y())) {
|
||||
menu_bar().on_mouse_move(m_cursor_manager);
|
||||
if (m_cursor_manager.is_changed<CursorManager::Params::Buttons>()) {
|
||||
menu_bar().on_mouse_status_change(m_cursor_manager);
|
||||
}
|
||||
return;
|
||||
} else if (menu_bar().is_hovered()) {
|
||||
menu_bar().on_mouse_leave(m_cursor_manager);
|
||||
}
|
||||
|
||||
Window* curr_hovered_window = nullptr;
|
||||
Window* window_under_mouse_ptr = nullptr;
|
||||
|
||||
for (auto* window_ptr : m_windows) {
|
||||
auto& window = *window_ptr;
|
||||
if (!window.visible()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (window.bounds().contains(m_cursor_manager.x(), m_cursor_manager.y())) {
|
||||
window_under_mouse_ptr = window_ptr;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_cursor_manager.pressed<CursorManager::Params::LeftButton>() || m_cursor_manager.pressed<CursorManager::Params::RightButton>()) {
|
||||
if (!window_under_mouse_ptr && m_active_window) {
|
||||
menu_bar().set_menubar_content(nullptr, m_compositor);
|
||||
m_compositor.invalidate(m_active_window->bounds());
|
||||
m_active_window->frame().set_active(false);
|
||||
set_active_window(nullptr);
|
||||
} else if (m_active_window != window_under_mouse_ptr) {
|
||||
set_active_window(window_under_mouse_ptr);
|
||||
}
|
||||
}
|
||||
|
||||
if (!window_under_mouse_ptr) {
|
||||
if (hovered_window()) {
|
||||
send_event(new MouseLeaveMessage(hovered_window()->connection_id(), hovered_window()->id(), 0, 0));
|
||||
}
|
||||
return;
|
||||
}
|
||||
auto& window = *window_under_mouse_ptr;
|
||||
|
||||
if (window.content_bounds().contains(m_cursor_manager.x(), m_cursor_manager.y())) {
|
||||
if (window.type() == WindowType::Standard && active_window() != &window) {
|
||||
curr_hovered_window = nullptr;
|
||||
} else {
|
||||
if (m_cursor_manager.is_changed<CursorManager::Params::Coords>()) {
|
||||
LG::Point<int> point(m_cursor_manager.x(), m_cursor_manager.y());
|
||||
point.offset_by(-window.content_bounds().origin());
|
||||
send_event(new MouseMoveMessage(window.connection_id(), window.id(), point.x(), point.y()));
|
||||
}
|
||||
curr_hovered_window = &window;
|
||||
}
|
||||
} else if (m_cursor_manager.is_changed<CursorManager::Params::LeftButton>() && m_cursor_manager.pressed<CursorManager::Params::LeftButton>()) {
|
||||
auto tap_point = LG::Point<int>(m_cursor_manager.x() - window.frame().bounds().min_x(), m_cursor_manager.y() - window.frame().bounds().min_y());
|
||||
window.frame().receive_tap_event(tap_point);
|
||||
start_window_move(window);
|
||||
}
|
||||
|
||||
if (hovered_window() && hovered_window() != curr_hovered_window) {
|
||||
send_event(new MouseLeaveMessage(hovered_window()->connection_id(), hovered_window()->id(), 0, 0));
|
||||
}
|
||||
set_hovered_window(curr_hovered_window);
|
||||
|
||||
if (hovered_window() && m_cursor_manager.is_changed<CursorManager::Params::Buttons>()) {
|
||||
LG::Point<int> point(m_cursor_manager.x(), m_cursor_manager.y());
|
||||
point.offset_by(-window.content_bounds().origin());
|
||||
|
||||
auto buttons_state = MouseActionState();
|
||||
if (m_cursor_manager.is_changed<CursorManager::Params::LeftButton>()) {
|
||||
// TODO: May be remove if?
|
||||
if (m_cursor_manager.pressed<CursorManager::Params::LeftButton>()) {
|
||||
buttons_state.set(MouseActionType::LeftMouseButtonPressed);
|
||||
} else {
|
||||
buttons_state.set(MouseActionType::LeftMouseButtonReleased);
|
||||
}
|
||||
}
|
||||
|
||||
send_event(new MouseActionMessage(window.connection_id(), window.id(), buttons_state.state(), point.x(), point.y()));
|
||||
}
|
||||
|
||||
if (hovered_window() && m_cursor_manager.is_changed<CursorManager::Params::Wheel>()) {
|
||||
auto* window = hovered_window();
|
||||
auto data = m_cursor_manager.get<CursorManager::Params::Wheel>();
|
||||
send_event(new MouseWheelMessage(window->connection_id(), window->id(), data, m_cursor_manager.x(), m_cursor_manager.y()));
|
||||
}
|
||||
}
|
||||
#elif TARGET_MOBILE
|
||||
void WindowManager::receive_mouse_event(std::unique_ptr<LFoundation::Event> event)
|
||||
{
|
||||
update_mouse_position(std::move(event));
|
||||
|
||||
if (m_compositor.control_bar().control_button_bounds().contains(m_cursor_manager.x(), m_cursor_manager.y()) && active_window()) {
|
||||
if (m_cursor_manager.pressed<CursorManager::Params::LeftButton>()) {
|
||||
switch (active_window()->type()) {
|
||||
case WindowType::Standard:
|
||||
remove_window(active_window());
|
||||
break;
|
||||
case WindowType::AppList:
|
||||
minimize_window(*active_window());
|
||||
break;
|
||||
|
||||
case WindowType::Homescreen:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Tap emulation
|
||||
if (m_cursor_manager.is_changed<CursorManager::Params::Buttons>() && active_window()) {
|
||||
auto window = active_window();
|
||||
auto buttons_state = MouseActionState();
|
||||
LG::Point<int> point(m_cursor_manager.x(), m_cursor_manager.y());
|
||||
point.offset_by(-window->content_bounds().origin());
|
||||
if (m_cursor_manager.is_changed<CursorManager::Params::LeftButton>()) {
|
||||
// TODO: May be remove if?
|
||||
if (m_cursor_manager.pressed<CursorManager::Params::LeftButton>()) {
|
||||
buttons_state.set(MouseActionType::LeftMouseButtonPressed);
|
||||
} else {
|
||||
buttons_state.set(MouseActionType::LeftMouseButtonReleased);
|
||||
}
|
||||
}
|
||||
send_event(new MouseActionMessage(window->connection_id(), window->id(), buttons_state.state(), point.x(), point.y()));
|
||||
}
|
||||
|
||||
if (active_window()) {
|
||||
auto window = active_window();
|
||||
if (m_cursor_manager.pressed<CursorManager::Params::LeftButton>()) {
|
||||
if (m_cursor_manager.is_changed<CursorManager::Params::Coords>()) {
|
||||
LG::Point<int> point(m_cursor_manager.x(), m_cursor_manager.y());
|
||||
point.offset_by(-window->content_bounds().origin());
|
||||
send_event(new MouseMoveMessage(window->connection_id(), window->id(), point.x(), point.y()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif // TARGET_MOBILE
|
||||
|
||||
void WindowManager::receive_keyboard_event(std::unique_ptr<LFoundation::Event> event)
|
||||
{
|
||||
auto* keyboard_event = reinterpret_cast<KeyboardEvent*>(event.release());
|
||||
if (active_window()) {
|
||||
auto window = active_window();
|
||||
send_event(new KeyboardMessage(window->connection_id(), window->id(), keyboard_event->packet().key));
|
||||
}
|
||||
delete keyboard_event;
|
||||
}
|
||||
|
||||
void WindowManager::receive_event(std::unique_ptr<LFoundation::Event> event)
|
||||
{
|
||||
switch (event->type()) {
|
||||
case WinServer::Event::Type::MouseEvent:
|
||||
receive_mouse_event(std::move(event));
|
||||
break;
|
||||
case WinServer::Event::Type::KeyboardEvent:
|
||||
receive_keyboard_event(std::move(event));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Notifiers
|
||||
|
||||
bool WindowManager::notify_listner_about_window_creation(const Window& win, int changed_window_id)
|
||||
{
|
||||
#ifdef WM_DEBUG
|
||||
Logger::debug << "notify_listner_about_window_status " << win.id() << " that " << changed_window_id << " " << type << std::endl;
|
||||
#endif
|
||||
auto* changed_window_ptr = window(changed_window_id);
|
||||
if (!changed_window_ptr) {
|
||||
return false;
|
||||
}
|
||||
send_event(new NotifyWindowCreateMessage(win.connection_id(), win.id(), changed_window_ptr->bundle_id(), changed_window_ptr->icon_path(), changed_window_id, changed_window_ptr->type()));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WindowManager::notify_listner_about_window_status(const Window& win, int changed_window_id, WindowStatusUpdateType type)
|
||||
{
|
||||
#ifdef WM_DEBUG
|
||||
Logger::debug << "notify_listner_about_window_status " << win.id() << " that " << changed_window_id << " " << type << std::endl;
|
||||
#endif
|
||||
auto* changed_window_ptr = window(changed_window_id);
|
||||
if (!changed_window_ptr) {
|
||||
return false;
|
||||
}
|
||||
send_event(new NotifyWindowStatusChangedMessage(win.connection_id(), win.id(), changed_window_id, (int)type));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WindowManager::notify_listner_about_changed_icon(const Window& win, int changed_window_id)
|
||||
{
|
||||
#ifdef WM_DEBUG
|
||||
Logger::debug << "notify_listner_about_changed_icon " << win.id() << " that " << changed_window_id << std::endl;
|
||||
#endif
|
||||
auto* changed_window_ptr = window(changed_window_id);
|
||||
if (!changed_window_ptr) {
|
||||
return false;
|
||||
}
|
||||
send_event(new NotifyWindowIconChangedMessage(win.connection_id(), win.id(), changed_window_id, changed_window_ptr->icon_path()));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WindowManager::notify_listner_about_changed_title(const Window& win, int changed_window_id)
|
||||
{
|
||||
#ifdef WM_DEBUG
|
||||
Logger::debug << "notify_listner_about_changed_title " << win.id() << " that " << changed_window_id << std::endl;
|
||||
#endif
|
||||
auto* changed_window_ptr = window(changed_window_id);
|
||||
if (!changed_window_ptr) {
|
||||
return false;
|
||||
}
|
||||
send_event(new NotifyWindowTitleChangedMessage(win.connection_id(), win.id(), changed_window_id, changed_window_ptr->app_title()));
|
||||
return true;
|
||||
}
|
||||
|
||||
void WindowManager::notify_window_creation(int changed_window_id)
|
||||
{
|
||||
for (auto* window_ptr : m_windows) {
|
||||
auto& window = *window_ptr;
|
||||
if (window.event_mask() & WindowEvent::WindowCreation) {
|
||||
notify_listner_about_window_creation(window, changed_window_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void WindowManager::notify_window_status_changed(int changed_window_id, WindowStatusUpdateType type)
|
||||
{
|
||||
for (auto* window_ptr : m_windows) {
|
||||
auto& window = *window_ptr;
|
||||
if (window.event_mask() & WindowEvent::WindowStatus) {
|
||||
notify_listner_about_window_status(window, changed_window_id, type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void WindowManager::notify_window_icon_changed(int changed_window_id)
|
||||
{
|
||||
for (auto* window_ptr : m_windows) {
|
||||
auto& window = *window_ptr;
|
||||
if (window.event_mask() & WindowEvent::IconChange) {
|
||||
notify_listner_about_changed_icon(window, changed_window_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void WindowManager::notify_window_title_changed(int changed_window_id)
|
||||
{
|
||||
for (auto* window_ptr : m_windows) {
|
||||
auto& window = *window_ptr;
|
||||
if (window.event_mask() & WindowEvent::TitleChange) {
|
||||
notify_listner_about_changed_title(window, changed_window_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef TARGET_DESKTOP
|
||||
void WindowManager::on_window_style_change(Window& window)
|
||||
{
|
||||
if (window.visible()) {
|
||||
window.frame().invalidate(m_compositor);
|
||||
}
|
||||
}
|
||||
#elif TARGET_MOBILE
|
||||
void WindowManager::on_window_style_change(Window& window)
|
||||
{
|
||||
if (active_window() == &window && window.type() == WindowType::Standard) {
|
||||
menu_bar().set_style(window.style());
|
||||
m_compositor.invalidate(menu_bar().bounds());
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void WindowManager::on_window_menubar_change(Window& window)
|
||||
{
|
||||
if (m_active_window == &window) {
|
||||
menu_bar().invalidate_menubar_panel(m_compositor);
|
||||
}
|
||||
}
|
||||
|
||||
void WindowManager::on_window_misbehave(Window& window, ViolationClass viocls)
|
||||
{
|
||||
switch (viocls) {
|
||||
case ViolationClass::Ignorable:
|
||||
case ViolationClass::Moderate:
|
||||
break;
|
||||
|
||||
case ViolationClass::Serious:
|
||||
// TODO: Currently we only remove the window, but all apps
|
||||
// should be stopped with with a signal.
|
||||
remove_window(&window);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace WinServer
|
||||
178
userland/servers/window_server/src/Managers/WindowManager.h
Normal file
178
userland/servers/window_server/src/Managers/WindowManager.h
Normal file
@@ -0,0 +1,178 @@
|
||||
#pragma once
|
||||
#include "../Components/ControlBar/ControlBar.h"
|
||||
#include "../Components/MenuBar/MenuBar.h"
|
||||
#include "../Components/Security/Violations.h"
|
||||
#include "../Devices/Screen.h"
|
||||
#include "../IPC/Connection.h"
|
||||
#include "../IPC/Event.h"
|
||||
#include "../IPC/ServerDecoder.h"
|
||||
#include "../Managers/Compositor.h"
|
||||
#include "../SystemApps/SystemApp.h"
|
||||
#include "../Target/Generic/Window.h"
|
||||
#include <algorithm>
|
||||
#include <libapi/window_server/Connections/WSConnection.h>
|
||||
#include <libfoundation/EventLoop.h>
|
||||
#include <libfoundation/EventReceiver.h>
|
||||
#include <libipc/ServerConnection.h>
|
||||
#include <list>
|
||||
#include <vector>
|
||||
|
||||
namespace WinServer {
|
||||
|
||||
class WindowManager : public LFoundation::EventReceiver {
|
||||
#ifdef TARGET_DESKTOP
|
||||
using Window = WinServer::Desktop::Window;
|
||||
#elif TARGET_MOBILE
|
||||
using Window = WinServer::Mobile::Window;
|
||||
#endif
|
||||
|
||||
public:
|
||||
inline static WindowManager& the()
|
||||
{
|
||||
extern WindowManager* s_WinServer_WindowManager_the;
|
||||
return *s_WinServer_WindowManager_the;
|
||||
}
|
||||
|
||||
WindowManager();
|
||||
|
||||
void add_window(Window* window);
|
||||
void remove_window(Window* window);
|
||||
|
||||
void resize_window(Window& window, const LG::Size& size);
|
||||
void close_window(Window& window) { send_event(new WindowCloseRequestMessage(window.connection_id(), window.id())); }
|
||||
void minimize_window(Window& window);
|
||||
void maximize_window(Window& window);
|
||||
|
||||
template <typename Callback>
|
||||
void minimize_windows(Callback callback)
|
||||
{
|
||||
std::vector<Window*> hided;
|
||||
for (auto* window : m_windows) {
|
||||
if (window && window->type() == WindowType::Standard && callback(window)) {
|
||||
window->set_visible(false);
|
||||
hided.push_back(window);
|
||||
on_window_became_invisible(window);
|
||||
remove_attention_from_window(window);
|
||||
}
|
||||
}
|
||||
|
||||
for (auto* window : hided) {
|
||||
m_windows.erase(std::find(m_windows.begin(), m_windows.end(), window));
|
||||
m_windows.push_back(window);
|
||||
}
|
||||
}
|
||||
|
||||
Window* top_window_in_view(WindowType type) const;
|
||||
inline Window* window(int id)
|
||||
{
|
||||
for (auto* window : m_windows) {
|
||||
if (window->id() == id) {
|
||||
return window;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
inline void move_window(Window* window, int x_offset, int y_offset)
|
||||
{
|
||||
y_offset = std::max(y_offset, (int)visible_area().min_y() - (int)Desktop::WindowFrame::std_top_border_frame_size() - window->bounds().min_y());
|
||||
if (m_dock.has_value()) [[likely]] {
|
||||
y_offset = std::min(y_offset, (int)(visible_area().max_y() - window->content_bounds().min_y()));
|
||||
}
|
||||
window->bounds().offset_by(x_offset, y_offset);
|
||||
window->content_bounds().offset_by(x_offset, y_offset);
|
||||
}
|
||||
|
||||
inline void do_bring_to_front(Window& window)
|
||||
{
|
||||
auto* window_ptr = &window;
|
||||
m_windows.erase(std::find(m_windows.begin(), m_windows.end(), window_ptr));
|
||||
m_windows.push_front(window_ptr);
|
||||
}
|
||||
void bring_to_front(Window& window);
|
||||
|
||||
inline std::list<Window*>& windows() { return m_windows; }
|
||||
inline const std::list<Window*>& windows() const { return m_windows; }
|
||||
inline int next_win_id() { return ++m_next_win_id; }
|
||||
|
||||
const LG::Rect& visible_area() const { return m_visible_area; }
|
||||
void shrink_visible_area(int top, int bottom) { m_visible_area.set_y(m_visible_area.min_y() + top), m_visible_area.set_height(m_visible_area.height() - top - bottom); }
|
||||
|
||||
void receive_event(std::unique_ptr<LFoundation::Event> event) override;
|
||||
|
||||
// Notifiers
|
||||
bool notify_listner_about_window_creation(const Window& window, int changed_window_id);
|
||||
bool notify_listner_about_window_status(const Window& window, int changed_window_id, WindowStatusUpdateType type);
|
||||
bool notify_listner_about_changed_icon(const Window&, int changed_window_id);
|
||||
bool notify_listner_about_changed_title(const Window&, int changed_window_id);
|
||||
|
||||
void notify_window_creation(int changed_window_id);
|
||||
void notify_window_status_changed(int changed_window_id, WindowStatusUpdateType type);
|
||||
void notify_window_icon_changed(int changed_window_id);
|
||||
void notify_window_title_changed(int changed_window_id);
|
||||
|
||||
inline void ask_to_set_active_window(Window* win) { set_active_window(win); }
|
||||
inline void ask_to_set_active_window(Window& win) { set_active_window(win); }
|
||||
|
||||
void on_window_misbehave(Window& window, ViolationClass);
|
||||
void on_window_style_change(Window& win);
|
||||
void on_window_menubar_change(Window& window);
|
||||
|
||||
// Popup & Menubar
|
||||
inline Popup& popup() { return m_compositor.popup(); }
|
||||
inline const Popup& popup() const { return m_compositor.popup(); }
|
||||
inline MenuBar& menu_bar() { return m_compositor.menu_bar(); }
|
||||
inline const MenuBar& menu_bar() const { return m_compositor.menu_bar(); }
|
||||
|
||||
private:
|
||||
void add_system_window(Window* window);
|
||||
void bring_system_windows_to_front();
|
||||
void setup_dock(Window* window);
|
||||
void setup_applist(Window* window);
|
||||
|
||||
void remove_attention_from_window(Window* window);
|
||||
|
||||
void start_window_move(Window& window);
|
||||
bool continue_window_move();
|
||||
|
||||
void update_mouse_position(std::unique_ptr<LFoundation::Event> mouse_event);
|
||||
void receive_mouse_event(std::unique_ptr<LFoundation::Event> event);
|
||||
void receive_keyboard_event(std::unique_ptr<LFoundation::Event> event);
|
||||
|
||||
inline Window* movable_window() { return m_movable_window; }
|
||||
inline Window* hovered_window() { return m_hovered_window; }
|
||||
inline void set_hovered_window(Window* win) { m_hovered_window = win; }
|
||||
|
||||
inline Window* active_window() { return m_active_window; }
|
||||
inline void set_active_window(Window* win) { on_active_window_will_change(), bring_to_front(*win), m_active_window = win, on_active_window_did_change(); }
|
||||
inline void set_active_window(Window& win) { on_active_window_will_change(), bring_to_front(win), m_active_window = &win, on_active_window_did_change(); }
|
||||
inline void set_active_window(std::nullptr_t) { on_active_window_will_change(), m_active_window = nullptr, on_active_window_did_change(); }
|
||||
|
||||
void on_window_became_invisible(Window* window);
|
||||
void on_active_window_will_change();
|
||||
void on_active_window_did_change();
|
||||
|
||||
inline void send_event(Message* msg) { m_event_loop.add(m_connection, new SendEvent(msg)); }
|
||||
|
||||
std::list<Window*> m_windows;
|
||||
|
||||
Screen& m_screen;
|
||||
Connection& m_connection;
|
||||
Compositor& m_compositor;
|
||||
CursorManager& m_cursor_manager;
|
||||
LFoundation::EventLoop& m_event_loop;
|
||||
std::vector<MenuDir> m_std_menubar_content;
|
||||
|
||||
LG::Rect m_visible_area;
|
||||
|
||||
SystemApp m_dock;
|
||||
SystemApp m_applist;
|
||||
|
||||
// TODO: implement with std::weak_ptr.
|
||||
Window* m_movable_window {};
|
||||
Window* m_active_window {};
|
||||
Window* m_hovered_window {};
|
||||
int m_next_win_id { 0 };
|
||||
};
|
||||
|
||||
} // namespace WinServer
|
||||
24
userland/servers/window_server/src/SystemApps/SystemApp.h
Normal file
24
userland/servers/window_server/src/SystemApps/SystemApp.h
Normal file
@@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
#include "../Target/Generic/Window.h"
|
||||
|
||||
namespace WinServer {
|
||||
|
||||
class SystemApp {
|
||||
public:
|
||||
SystemApp() = default;
|
||||
~SystemApp() = default;
|
||||
|
||||
void set_window(Window* win) { m_window = win; }
|
||||
Window* window() { return m_window; }
|
||||
const Window* window() const { return m_window; }
|
||||
|
||||
bool has_value() const { return !!m_window; }
|
||||
operator bool() const { return has_value(); }
|
||||
|
||||
private:
|
||||
bool visible;
|
||||
Window* m_window {};
|
||||
};
|
||||
|
||||
}
|
||||
76
userland/servers/window_server/src/Target/Desktop/Window.cpp
Normal file
76
userland/servers/window_server/src/Target/Desktop/Window.cpp
Normal file
@@ -0,0 +1,76 @@
|
||||
#include "Window.h"
|
||||
#include "../../Managers/WindowManager.h"
|
||||
#include <utility>
|
||||
|
||||
namespace WinServer::Desktop {
|
||||
|
||||
Window::Window(int connection_id, int id, CreateWindowMessage& msg)
|
||||
: BaseWindow(connection_id, id, msg)
|
||||
, m_frame(*this)
|
||||
{
|
||||
m_bounds = LG::Rect(0, 0, msg.width() + frame().left_border_size() + frame().right_border_size(), msg.height() + frame().top_border_size() + frame().bottom_border_size());
|
||||
m_content_bounds = LG::Rect(m_frame.left_border_size(), m_frame.top_border_size(), msg.width(), msg.height());
|
||||
m_content_bitmap = LG::PixelBitmap(m_buffer.data(), content_bounds().width(), content_bounds().height());
|
||||
|
||||
// Creating standard menubar directory entry.
|
||||
m_menubar_content.push_back(MenuDir(m_app_name, 0));
|
||||
m_menubar_content[0].add_item(PopupItem { PopupItem::InternalId, "Minimize others", [this](int) { WindowManager::the().minimize_windows([this](Window* win) { return win != this; }); } });
|
||||
m_menubar_content[0].add_item(PopupItem { PopupItem::InternalId, "Minimize", [this](int) { WindowManager::the().minimize_window(*this); } });
|
||||
m_menubar_content[0].add_item(PopupItem { PopupItem::InternalId, "Close", [this](int) { WindowManager::the().close_window(*this); } });
|
||||
}
|
||||
|
||||
Window::Window(Window&& win)
|
||||
: BaseWindow(std::move(win))
|
||||
, m_frame(*this, std::move(win.m_frame.control_panel_buttons()), std::move(win.m_frame.window_control_buttons()))
|
||||
, m_corner_mask(std::move(win.m_corner_mask))
|
||||
, m_menubar_content(std::move(win.m_menubar_content))
|
||||
{
|
||||
}
|
||||
|
||||
void Window::make_frame()
|
||||
{
|
||||
uint32_t x = m_bounds.min_x();
|
||||
uint32_t y = m_bounds.min_y();
|
||||
uint32_t content_width = m_content_bounds.width();
|
||||
uint32_t content_height = m_content_bounds.height();
|
||||
m_bounds = LG::Rect(x, y, content_width + frame().left_border_size() + frame().right_border_size(), content_height + frame().top_border_size() + frame().bottom_border_size());
|
||||
m_content_bounds = LG::Rect(x + m_frame.left_border_size(), y + m_frame.top_border_size(), content_width, content_height);
|
||||
m_frame.set_visible(true);
|
||||
}
|
||||
|
||||
void Window::make_frameless()
|
||||
{
|
||||
uint32_t x = m_bounds.min_x();
|
||||
uint32_t y = m_bounds.min_y();
|
||||
uint32_t content_width = m_content_bounds.width();
|
||||
uint32_t content_height = m_content_bounds.height();
|
||||
m_bounds = LG::Rect(x, y, content_width, content_height);
|
||||
m_content_bounds = LG::Rect(x, y, content_width, content_height);
|
||||
m_frame.set_visible(false);
|
||||
}
|
||||
|
||||
void Window::recalc_bounds(const LG::Size& size)
|
||||
{
|
||||
m_content_bounds.set_width(size.width());
|
||||
m_content_bounds.set_height(size.height());
|
||||
|
||||
m_bounds.set_width(size.width() + frame().left_border_size() + frame().right_border_size());
|
||||
m_bounds.set_height(size.height() + frame().top_border_size() + frame().bottom_border_size());
|
||||
}
|
||||
|
||||
void Window::did_size_change(const LG::Size& size)
|
||||
{
|
||||
recalc_bounds(size);
|
||||
}
|
||||
|
||||
void Window::on_style_change()
|
||||
{
|
||||
WindowManager::the().on_window_style_change(*this);
|
||||
}
|
||||
|
||||
void Window::on_menubar_change()
|
||||
{
|
||||
WindowManager::the().on_window_menubar_change(*this);
|
||||
}
|
||||
|
||||
} // namespace WinServer
|
||||
47
userland/servers/window_server/src/Target/Desktop/Window.h
Normal file
47
userland/servers/window_server/src/Target/Desktop/Window.h
Normal file
@@ -0,0 +1,47 @@
|
||||
#pragma once
|
||||
#include "../../Components/Base/BaseWindow.h"
|
||||
#include "../../Components/MenuBar/MenuItem.h"
|
||||
#include "../../IPC/Connection.h"
|
||||
#include "WindowFrame.h"
|
||||
#include <libfoundation/SharedBuffer.h>
|
||||
#include <libg/PixelBitmap.h>
|
||||
#include <libg/Rect.h>
|
||||
#include <sys/types.h>
|
||||
#include <utility>
|
||||
|
||||
namespace WinServer::Desktop {
|
||||
|
||||
class Window : public BaseWindow {
|
||||
public:
|
||||
Window(int connection_id, int id, CreateWindowMessage& msg);
|
||||
Window(Window&& win);
|
||||
|
||||
inline WindowFrame& frame() { return m_frame; }
|
||||
inline const WindowFrame& frame() const { return m_frame; }
|
||||
|
||||
inline const LG::CornerMask& corner_mask() const { return m_corner_mask; }
|
||||
|
||||
inline std::vector<MenuDir>& menubar_content() { return m_menubar_content; }
|
||||
inline const std::vector<MenuDir>& menubar_content() const { return m_menubar_content; }
|
||||
|
||||
void on_menubar_change();
|
||||
|
||||
virtual void did_app_title_change() override { m_frame.on_set_app_title(); }
|
||||
virtual void did_icon_path_change() override { m_frame.on_set_icon(); }
|
||||
virtual void did_size_change(const LG::Size& size) override;
|
||||
|
||||
inline void set_style(StatusBarStyle style) { m_frame.set_style(style), on_style_change(); }
|
||||
|
||||
void make_frame();
|
||||
void make_frameless();
|
||||
|
||||
private:
|
||||
void recalc_bounds(const LG::Size& size);
|
||||
void on_style_change();
|
||||
|
||||
WindowFrame m_frame;
|
||||
LG::CornerMask m_corner_mask { LG::CornerMask::SystemRadius, LG::CornerMask::NonMasked, LG::CornerMask::Masked };
|
||||
std::vector<MenuDir> m_menubar_content;
|
||||
};
|
||||
|
||||
} // namespace WinServer
|
||||
@@ -0,0 +1,232 @@
|
||||
#include "WindowFrame.h"
|
||||
#include "../../Components/Elements/Button.h"
|
||||
#include "../../Managers/WindowManager.h"
|
||||
#include "Window.h"
|
||||
#include <libg/Font.h>
|
||||
#include <libg/ImageLoaders/PNGLoader.h>
|
||||
#include <libg/Rect.h>
|
||||
#include <utility>
|
||||
|
||||
#define CONTROL_PANEL_CLOSE 0x0
|
||||
#define CONTROL_PANEL_MAXIMIZE 0x1
|
||||
#define CONTROL_PANEL_MINIMIZE 0x2
|
||||
|
||||
namespace WinServer::Desktop {
|
||||
|
||||
static const uint32_t s_close_button_glyph_data[10] = {
|
||||
0b1100000011,
|
||||
0b1110000111,
|
||||
0b0111001110,
|
||||
0b0011111100,
|
||||
0b0001111000,
|
||||
0b0001111000,
|
||||
0b0011111100,
|
||||
0b0111001110,
|
||||
0b1110000111,
|
||||
0b1100000011,
|
||||
};
|
||||
|
||||
static const uint32_t s_maximise_button_glyph_data[10] = {
|
||||
0b1111100000,
|
||||
0b1111000000,
|
||||
0b1110000000,
|
||||
0b1100000000,
|
||||
0b1000000000,
|
||||
0b0000000001,
|
||||
0b0000000011,
|
||||
0b0000000111,
|
||||
0b0000001111,
|
||||
0b0000011111
|
||||
};
|
||||
|
||||
static const uint32_t s_minimise_button_glyph_data[10] = {
|
||||
0b0000000000,
|
||||
0b0000000000,
|
||||
0b0000000000,
|
||||
0b0000000000,
|
||||
0b0000000000,
|
||||
0b0000000000,
|
||||
0b0000000000,
|
||||
0b0000000000,
|
||||
0b1111111111,
|
||||
0b1111111111
|
||||
};
|
||||
|
||||
WindowFrame::WindowFrame(Window& window)
|
||||
: m_window(window)
|
||||
, m_window_control_buttons()
|
||||
, m_control_panel_buttons()
|
||||
{
|
||||
set_style(StatusBarStyle::StandardLight);
|
||||
LG::GlyphMetrics metrics = {
|
||||
.width = 10,
|
||||
.height = 10,
|
||||
.top_bearing = 10,
|
||||
.left_bearing = 0,
|
||||
.baseline = 0,
|
||||
.advance = 10,
|
||||
.font_size = 10
|
||||
};
|
||||
|
||||
auto* close = new Button();
|
||||
close->set_icon(LG::Glyph(s_close_button_glyph_data, metrics, LG::Glyph::ConstDataMarker {}));
|
||||
auto* maximize = new Button();
|
||||
maximize->set_icon(LG::Glyph(s_maximise_button_glyph_data, metrics, LG::Glyph::ConstDataMarker {}));
|
||||
auto* minimize = new Button();
|
||||
minimize->set_icon(LG::Glyph(s_minimise_button_glyph_data, metrics, LG::Glyph::ConstDataMarker {}));
|
||||
|
||||
close->set_title_color(LG::Color(196, 128, 128));
|
||||
|
||||
m_window_control_buttons.push_back(close);
|
||||
m_window_control_buttons.push_back(maximize);
|
||||
m_window_control_buttons.push_back(minimize);
|
||||
}
|
||||
|
||||
WindowFrame::WindowFrame(Window& window, std::vector<Button*>&& control_panel_buttons, std::vector<Button*>&& window_control_buttons)
|
||||
: m_window(window)
|
||||
, m_window_control_buttons(std::move(window_control_buttons))
|
||||
, m_control_panel_buttons(std::move(control_panel_buttons))
|
||||
{
|
||||
}
|
||||
|
||||
void WindowFrame::on_set_app_title()
|
||||
{
|
||||
m_app_title_width = Helpers::text_width(m_window.app_title(), LG::Font::system_font());
|
||||
if (style().show_text()) {
|
||||
WinServer::Compositor::the().invalidate(bounds());
|
||||
}
|
||||
}
|
||||
|
||||
void WindowFrame::add_control(const std::string& title)
|
||||
{
|
||||
auto* new_control = new Button();
|
||||
new_control->set_title(title);
|
||||
m_control_panel_buttons.push_back(new_control);
|
||||
WinServer::Compositor::the().invalidate(bounds());
|
||||
}
|
||||
|
||||
void WindowFrame::draw(LG::Context& ctx)
|
||||
{
|
||||
if (!visible()) {
|
||||
return;
|
||||
}
|
||||
int x = m_window.bounds().min_x();
|
||||
int y = m_window.bounds().min_y();
|
||||
size_t width = m_window.bounds().width();
|
||||
size_t height = m_window.bounds().height();
|
||||
|
||||
int left_x = x + left_border_size();
|
||||
int right_x = x + width - right_border_size();
|
||||
int bottom_y = y + height - bottom_border_size();
|
||||
|
||||
// Drawing frame and shadings
|
||||
int shadow_spread = LG::Shading::SystemSpread;
|
||||
ctx.set_fill_color(style().color());
|
||||
ctx.fill_rounded(LG::Rect(x + left_border_size(), y + std_top_border_frame_size(), width - 2 * left_border_size(), top_border_size() - std_top_border_frame_size()), LG::CornerMask(4, true, false));
|
||||
ctx.set_fill_color(Color::Shadow);
|
||||
const auto shading_rect = LG::Rect(x + left_border_size(), y + std_top_border_frame_size(), width - 2 * left_border_size(), height - std_top_border_frame_size() - std_bottom_border_size());
|
||||
ctx.draw_box_shading(shading_rect, LG::Shading(LG::Shading::Type::Box, 0, shadow_spread), LG::CornerMask(LG::CornerMask::SystemRadius));
|
||||
|
||||
// Drawing labels, icons.
|
||||
// Drawing positions are calculated using a start of the frame.
|
||||
ctx.set_fill_color(m_text_colors[(int)active()]);
|
||||
if (style().show_text()) {
|
||||
Helpers::draw_text(ctx, { left_x + spacing(), y + text_y_offset() }, m_window.app_title(), LG::Font::system_font());
|
||||
}
|
||||
|
||||
int start_controls_offset = m_app_title_width + 2 * spacing();
|
||||
int start_controls = left_x + start_controls_offset;
|
||||
for (int i = 0; i < m_control_panel_buttons.size(); i++) {
|
||||
m_control_panel_buttons[i]->display(ctx, { start_controls, y + text_y_offset() });
|
||||
start_controls += spacing() + m_control_panel_buttons[i]->bounds().width();
|
||||
}
|
||||
|
||||
int start_buttons = right_x - spacing() - m_window_control_buttons[0]->bounds().width();
|
||||
for (int i = 0; i < m_window_control_buttons.size(); i++) {
|
||||
if (active() && i == 0) {
|
||||
ctx.set_fill_color(m_window_control_buttons[i]->title_color());
|
||||
} else {
|
||||
ctx.set_fill_color(m_text_colors[(int)active()]);
|
||||
}
|
||||
|
||||
m_window_control_buttons[i]->display(ctx, { start_buttons, y + button_y_offset() });
|
||||
start_buttons += -spacing() - m_window_control_buttons[i]->bounds().width();
|
||||
}
|
||||
}
|
||||
|
||||
void WindowFrame::invalidate(WinServer::Compositor& compositor) const
|
||||
{
|
||||
if (!visible()) {
|
||||
return;
|
||||
}
|
||||
int x = m_window.bounds().min_x();
|
||||
int y = m_window.bounds().min_y();
|
||||
size_t width = m_window.bounds().width();
|
||||
size_t height = m_window.bounds().height();
|
||||
int right_x = x + width - right_border_size();
|
||||
int bottom_y = y + height - bottom_border_size();
|
||||
compositor.invalidate(LG::Rect(x, y, width, top_border_size()));
|
||||
compositor.invalidate(LG::Rect(x, y, left_border_size(), height));
|
||||
compositor.invalidate(LG::Rect(right_x, y, right_border_size(), height));
|
||||
compositor.invalidate(LG::Rect(x, bottom_y, width, bottom_border_size()));
|
||||
}
|
||||
|
||||
void WindowFrame::receive_tap_event(const LG::Point<int>& tap)
|
||||
{
|
||||
// Calculating buttons' positions
|
||||
size_t width = m_window.bounds().width();
|
||||
int right_x = width - right_border_size();
|
||||
int start_buttons = right_x - spacing() - m_window_control_buttons[0]->bounds().width();
|
||||
for (int button_id = 0; button_id < m_window_control_buttons.size(); button_id++) {
|
||||
auto button_frame = m_window_control_buttons[button_id]->bounds();
|
||||
button_frame.offset_by(start_buttons, button_y_offset());
|
||||
if (button_frame.contains(tap)) {
|
||||
handle_control_panel_tap(button_id);
|
||||
return;
|
||||
}
|
||||
start_buttons += -spacing() - button_frame.width();
|
||||
}
|
||||
}
|
||||
|
||||
const LG::Rect WindowFrame::bounds() const
|
||||
{
|
||||
const auto& bounds = m_window.bounds();
|
||||
return LG::Rect(bounds.min_x(), bounds.min_y(), bounds.width(), top_border_size());
|
||||
}
|
||||
|
||||
void WindowFrame::handle_control_panel_tap(int button_id)
|
||||
{
|
||||
auto& wm = WindowManager::the();
|
||||
switch (button_id) {
|
||||
case CONTROL_PANEL_CLOSE:
|
||||
wm.close_window(m_window);
|
||||
break;
|
||||
case CONTROL_PANEL_MAXIMIZE:
|
||||
wm.maximize_window(m_window);
|
||||
break;
|
||||
case CONTROL_PANEL_MINIMIZE:
|
||||
wm.minimize_window(m_window);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void WindowFrame::set_style(StatusBarStyle ts)
|
||||
{
|
||||
m_style = ts;
|
||||
|
||||
if (m_style.dark_text()) {
|
||||
m_text_colors[0] = Color::InactiveText;
|
||||
m_text_colors[1] = LG::Color::DarkSystemText;
|
||||
} else {
|
||||
m_text_colors[0] = Color::InactiveText;
|
||||
m_text_colors[1] = LG::Color::LightSystemText;
|
||||
}
|
||||
}
|
||||
|
||||
void WindowFrame::on_set_icon()
|
||||
{
|
||||
}
|
||||
|
||||
} // namespace WinServer
|
||||
@@ -0,0 +1,87 @@
|
||||
#pragma once
|
||||
#include "../../Components/Elements/Button.h"
|
||||
#include "../../Constants/Colors.h"
|
||||
#include "../../IPC/Event.h"
|
||||
#include <libapi/window_server/MessageContent/MenuBar.h>
|
||||
#include <libfoundation/EventReceiver.h>
|
||||
#include <libg/Context.h>
|
||||
#include <libg/PixelBitmap.h>
|
||||
|
||||
namespace WinServer {
|
||||
class Compositor;
|
||||
}
|
||||
|
||||
namespace WinServer::Desktop {
|
||||
|
||||
class Window;
|
||||
|
||||
class WindowFrame {
|
||||
public:
|
||||
explicit WindowFrame(Window& window);
|
||||
WindowFrame(Window& window, std::vector<Button*>&& control_panel_buttons, std::vector<Button*>&& window_control_buttons);
|
||||
~WindowFrame() = default;
|
||||
|
||||
void draw(LG::Context&);
|
||||
static constexpr size_t std_app_header_size() { return 26; }
|
||||
static constexpr size_t std_top_border_frame_size() { return 4; }
|
||||
static constexpr size_t std_top_border_size() { return std_top_border_frame_size() + std_app_header_size(); }
|
||||
static constexpr size_t std_bottom_border_size() { return 4; }
|
||||
static constexpr size_t std_left_border_size() { return 4; }
|
||||
static constexpr size_t std_right_border_size() { return 4; }
|
||||
inline size_t top_border_size() const { return m_top_border_size; }
|
||||
inline size_t bottom_border_size() const { return std_bottom_border_size(); }
|
||||
inline size_t left_border_size() const { return std_left_border_size(); }
|
||||
inline size_t right_border_size() const { return std_right_border_size(); }
|
||||
|
||||
const LG::Rect bounds() const;
|
||||
|
||||
void receive_tap_event(const LG::Point<int>& tap);
|
||||
|
||||
void on_set_app_title();
|
||||
void add_control(const std::string& title);
|
||||
|
||||
inline std::vector<Button*>& window_control_buttons() { return m_window_control_buttons; }
|
||||
inline const std::vector<Button*>& window_control_buttons() const { return m_window_control_buttons; }
|
||||
|
||||
inline std::vector<Button*>& control_panel_buttons() { return m_control_panel_buttons; }
|
||||
inline const std::vector<Button*>& control_panel_buttons() const { return m_control_panel_buttons; }
|
||||
void handle_control_panel_tap(int button_id);
|
||||
|
||||
inline StatusBarStyle style() const { return m_style; }
|
||||
void set_style(StatusBarStyle ts);
|
||||
|
||||
void set_visible(bool visible)
|
||||
{
|
||||
m_top_border_size = visible ? std_top_border_size() : 0;
|
||||
m_visible = visible;
|
||||
}
|
||||
|
||||
bool visible() const { return m_visible; }
|
||||
void set_active(bool active) { m_active = active; }
|
||||
bool active() const { return m_active; }
|
||||
|
||||
void invalidate(WinServer::Compositor& compositor) const;
|
||||
|
||||
void on_set_icon();
|
||||
|
||||
static constexpr int spacing() { return 8; }
|
||||
static constexpr int icon_width() { return 12; }
|
||||
static constexpr int icon_y_offset() { return 7 + std_top_border_frame_size(); }
|
||||
static constexpr int text_y_offset() { return 9 + std_top_border_frame_size(); }
|
||||
static constexpr int button_y_offset() { return 8 + std_top_border_frame_size(); }
|
||||
|
||||
private:
|
||||
Window& m_window;
|
||||
std::vector<Button*> m_window_control_buttons;
|
||||
std::vector<Button*> m_control_panel_buttons;
|
||||
LG::Color m_text_colors[2];
|
||||
LG::Color m_color { LG::Color::LightSystemBackground };
|
||||
size_t m_top_border_size { std_top_border_size() };
|
||||
size_t m_app_title_width { 0 };
|
||||
bool m_visible { true };
|
||||
bool m_active { true };
|
||||
|
||||
StatusBarStyle m_style;
|
||||
};
|
||||
|
||||
} // namespace WinServer
|
||||
14
userland/servers/window_server/src/Target/Generic/Window.h
Normal file
14
userland/servers/window_server/src/Target/Generic/Window.h
Normal file
@@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
#include "../Desktop/Window.h"
|
||||
#include "../Mobile/Window.h"
|
||||
|
||||
namespace WinServer {
|
||||
|
||||
#ifdef TARGET_DESKTOP
|
||||
using Window = Desktop::Window;
|
||||
#elif TARGET_MOBILE
|
||||
using Window = Mobile::Window;
|
||||
#endif
|
||||
|
||||
} // namespace WinServer
|
||||
25
userland/servers/window_server/src/Target/Mobile/Window.cpp
Normal file
25
userland/servers/window_server/src/Target/Mobile/Window.cpp
Normal file
@@ -0,0 +1,25 @@
|
||||
#include "Window.h"
|
||||
#include "../../Managers/WindowManager.h"
|
||||
#include <utility>
|
||||
|
||||
namespace WinServer::Mobile {
|
||||
|
||||
Window::Window(int connection_id, int id, CreateWindowMessage& msg)
|
||||
: BaseWindow(connection_id, id, msg)
|
||||
{
|
||||
m_bounds = LG::Rect(0, 0, msg.width(), msg.height());
|
||||
m_content_bounds = LG::Rect(0, 0, msg.width(), msg.height());
|
||||
m_content_bitmap = LG::PixelBitmap(m_buffer.data(), content_bounds().width(), content_bounds().height());
|
||||
}
|
||||
|
||||
Window::Window(Window&& win)
|
||||
: BaseWindow(std::move(win))
|
||||
{
|
||||
}
|
||||
|
||||
void Window::on_style_change()
|
||||
{
|
||||
WindowManager::the().on_window_style_change(*this);
|
||||
}
|
||||
|
||||
} // namespace WinServer
|
||||
27
userland/servers/window_server/src/Target/Mobile/Window.h
Normal file
27
userland/servers/window_server/src/Target/Mobile/Window.h
Normal file
@@ -0,0 +1,27 @@
|
||||
#pragma once
|
||||
#include "../../Components/Base/BaseWindow.h"
|
||||
#include "../../Components/MenuBar/MenuItem.h"
|
||||
#include "../../IPC/Connection.h"
|
||||
#include <libfoundation/SharedBuffer.h>
|
||||
#include <libg/PixelBitmap.h>
|
||||
#include <libg/Rect.h>
|
||||
#include <sys/types.h>
|
||||
#include <utility>
|
||||
|
||||
namespace WinServer::Mobile {
|
||||
|
||||
class Window : public BaseWindow {
|
||||
public:
|
||||
Window(int connection_id, int id, CreateWindowMessage& msg);
|
||||
Window(Window&& win);
|
||||
|
||||
inline void set_style(StatusBarStyle ts) { m_style = ts, on_style_change(); }
|
||||
inline StatusBarStyle style() { return m_style; }
|
||||
|
||||
private:
|
||||
void on_style_change();
|
||||
|
||||
StatusBarStyle m_style { StatusBarStyle::StandardLight };
|
||||
};
|
||||
|
||||
} // namespace WinServer
|
||||
32
userland/servers/window_server/src/main.cpp
Normal file
32
userland/servers/window_server/src/main.cpp
Normal file
@@ -0,0 +1,32 @@
|
||||
#include "Managers/InitManager.h"
|
||||
|
||||
#ifdef TARGET_DESKTOP
|
||||
#define LAUNCH_PATH "/System/dock"
|
||||
#elif TARGET_MOBILE
|
||||
#define LAUNCH_PATH "/System/homescreen"
|
||||
#endif
|
||||
|
||||
int main()
|
||||
{
|
||||
WinServer::InitManager::load_screen();
|
||||
|
||||
auto* event_loop = new LFoundation::EventLoop();
|
||||
WinServer::InitManager::load_core_component<WinServer::Connection>();
|
||||
WinServer::InitManager::load_core_component<WinServer::CursorManager>();
|
||||
WinServer::InitManager::load_core_component<WinServer::ResourceManager, 4>();
|
||||
WinServer::InitManager::load_core_component<WinServer::Popup>();
|
||||
WinServer::InitManager::load_core_component<WinServer::MenuBar>();
|
||||
#ifdef TARGET_MOBILE
|
||||
WinServer::InitManager::load_core_component<WinServer::ControlBar>();
|
||||
#endif
|
||||
WinServer::InitManager::load_core_component<WinServer::Compositor>();
|
||||
WinServer::InitManager::load_core_component<WinServer::WindowManager>();
|
||||
WinServer::InitManager::load_core_component<WinServer::Devices>();
|
||||
|
||||
WinServer::InitManager::add_widget<WinServer::Clock>();
|
||||
|
||||
WinServer::InitManager::launch_app(LAUNCH_PATH, 10);
|
||||
|
||||
WinServer::LoadingScreen::destroy_the();
|
||||
return event_loop->run();
|
||||
}
|
||||
5
userland/shell/xsh/.info.mk
Normal file
5
userland/shell/xsh/.info.mk
Normal file
@@ -0,0 +1,5 @@
|
||||
APPS += XSH
|
||||
|
||||
XSH_NAME = xsh
|
||||
XSH_LIBS = c
|
||||
XSH_INSTALL_PATH = bin/
|
||||
8
userland/shell/xsh/BUILD.gn
Normal file
8
userland/shell/xsh/BUILD.gn
Normal file
@@ -0,0 +1,8 @@
|
||||
import("//build/userland/TEMPLATE.gni")
|
||||
|
||||
xOS_executable("xsh") {
|
||||
install_path = "bin/"
|
||||
sources = [ "main.c" ]
|
||||
configs = [ "//build/userland:userland_flags" ]
|
||||
deplibs = [ "libc" ]
|
||||
}
|
||||
190
userland/shell/xsh/main.c
Normal file
190
userland/shell/xsh/main.c
Normal file
@@ -0,0 +1,190 @@
|
||||
#include <pthread.h>
|
||||
#include <signal.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#define true (1)
|
||||
#define false (0)
|
||||
|
||||
char* _cmd_app;
|
||||
char* _cmd_buffer;
|
||||
char** _cmd_parsed_buffer;
|
||||
static int _cmd_buffer_position = 0;
|
||||
static int _cmd_parsed_buffer_position = 0;
|
||||
static int running_job = 0;
|
||||
|
||||
uint32_t _is_cmd_internal();
|
||||
void _cmd_buffer_clear();
|
||||
void _cmd_loop();
|
||||
void _cmd_loop_start();
|
||||
void _cmd_loop_end();
|
||||
void _cmd_input();
|
||||
void _cmd_processor();
|
||||
char _cmd_is_ascii(uint32_t key);
|
||||
char _cmd_cmp_command(const char*);
|
||||
int16_t _cmd_find_cmd_handler();
|
||||
uint32_t _cmd_getkeycode();
|
||||
|
||||
enum internal_cmd_code {
|
||||
CMD_NONE = 0,
|
||||
CMD_CD,
|
||||
CMD_SET,
|
||||
CMD_UNSET,
|
||||
CMD_ENV,
|
||||
};
|
||||
|
||||
uint32_t _is_cmd_internal()
|
||||
{
|
||||
if (memcmp(_cmd_buffer, "cd", 2) == 0) {
|
||||
return CMD_CD;
|
||||
}
|
||||
if (memcmp(_cmd_buffer, "set", 3) == 0) {
|
||||
return CMD_SET;
|
||||
}
|
||||
if (memcmp(_cmd_buffer, "env", 3) == 0) {
|
||||
return CMD_ENV;
|
||||
}
|
||||
if (memcmp(_cmd_buffer, "unset", 5) == 0) {
|
||||
return CMD_UNSET;
|
||||
}
|
||||
return CMD_NONE;
|
||||
}
|
||||
|
||||
int _cmd_do_internal(uint32_t code)
|
||||
{
|
||||
if (code == CMD_CD) {
|
||||
return chdir(_cmd_parsed_buffer[1]);
|
||||
}
|
||||
if (code == CMD_SET) {
|
||||
char* eqpos = strchr(_cmd_parsed_buffer[1], '=');
|
||||
if (!eqpos) {
|
||||
return 0;
|
||||
}
|
||||
*eqpos = '\0';
|
||||
return setenv(_cmd_parsed_buffer[1], (char*)(eqpos + 1), 1);
|
||||
}
|
||||
if (code == CMD_UNSET) {
|
||||
return unsetenv(_cmd_parsed_buffer[1]);
|
||||
}
|
||||
if (code == CMD_ENV) {
|
||||
for (int i = 0; environ[i]; i++) {
|
||||
const char* envv = environ[i];
|
||||
char* eqpos = strchr(envv, '=');
|
||||
if (!eqpos) {
|
||||
continue;
|
||||
}
|
||||
size_t len = strlen(envv);
|
||||
write(STDOUT, envv, len);
|
||||
write(STDOUT, "\n", 1);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void _cmd_buffer_clear()
|
||||
{
|
||||
_cmd_buffer_position = 0;
|
||||
}
|
||||
|
||||
void _cmd_loop()
|
||||
{
|
||||
for (;;) {
|
||||
_cmd_loop_start();
|
||||
_cmd_input();
|
||||
_cmd_processor();
|
||||
_cmd_loop_end();
|
||||
}
|
||||
}
|
||||
|
||||
void _cmd_input()
|
||||
{
|
||||
_cmd_buffer_position = read(STDIN, _cmd_buffer, 256);
|
||||
|
||||
while (_cmd_buffer_position < 0)
|
||||
_cmd_buffer_position = read(STDIN, _cmd_buffer, 256);
|
||||
}
|
||||
|
||||
void _cmd_processor()
|
||||
{
|
||||
_cmd_parsed_buffer_position = 0;
|
||||
|
||||
char is_prev_space = true;
|
||||
|
||||
for (int i = 0; i < _cmd_buffer_position; i++) {
|
||||
if (_cmd_buffer[i] == ' ') {
|
||||
if (is_prev_space == false) {
|
||||
/* null terminator when args are sent */
|
||||
_cmd_buffer[i] = '\0';
|
||||
}
|
||||
is_prev_space = true;
|
||||
} else {
|
||||
if (is_prev_space) {
|
||||
_cmd_parsed_buffer[_cmd_parsed_buffer_position++] = (char*)((char*)_cmd_buffer + i);
|
||||
}
|
||||
is_prev_space = false;
|
||||
}
|
||||
}
|
||||
|
||||
/* Remove \n */
|
||||
_cmd_buffer[_cmd_buffer_position - 1] = '\0';
|
||||
_cmd_parsed_buffer[_cmd_parsed_buffer_position] = 0;
|
||||
|
||||
/* We try to launch an app */
|
||||
uint32_t cmd = _is_cmd_internal();
|
||||
if (cmd == CMD_NONE) {
|
||||
uint32_t namelen = strlen(_cmd_parsed_buffer[0]);
|
||||
memcpy(_cmd_app + 5, _cmd_buffer, namelen + 1);
|
||||
|
||||
int res = fork();
|
||||
if (!res) {
|
||||
// We don't pass an app name to args.
|
||||
execve(_cmd_app, &_cmd_parsed_buffer[0], environ);
|
||||
exit(-1);
|
||||
} else {
|
||||
running_job = res;
|
||||
wait(res);
|
||||
}
|
||||
} else {
|
||||
_cmd_do_internal(cmd);
|
||||
}
|
||||
}
|
||||
|
||||
void _cmd_loop_start()
|
||||
{
|
||||
write(1, "> ", 2);
|
||||
}
|
||||
|
||||
void _cmd_loop_end()
|
||||
{
|
||||
_cmd_buffer_clear();
|
||||
write(1, "\n", 1);
|
||||
}
|
||||
|
||||
char _cmd_is_ascii(uint32_t key)
|
||||
{
|
||||
return 32 <= key && key <= 126;
|
||||
}
|
||||
|
||||
void inter(int no)
|
||||
{
|
||||
kill(running_job, 9);
|
||||
return;
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
sigaction(3, inter);
|
||||
ioctl(0, TIOCSPGRP, getpgid(getpid()));
|
||||
ioctl(0, TIOCGPGRP, 0);
|
||||
_cmd_app = malloc(256);
|
||||
_cmd_buffer = malloc(256);
|
||||
_cmd_parsed_buffer = malloc(256 * sizeof(char*));
|
||||
memcpy(_cmd_app, "/bin/", 5);
|
||||
_cmd_loop();
|
||||
|
||||
return 0;
|
||||
}
|
||||
24
userland/system/applist/AppDelegate.cpp
Normal file
24
userland/system/applist/AppDelegate.cpp
Normal file
@@ -0,0 +1,24 @@
|
||||
#include "AppListView.h"
|
||||
#include "AppListViewController.h"
|
||||
#include "AppListWindow.h"
|
||||
#include <libui/AppDelegate.h>
|
||||
|
||||
class AppDelegate : public UI::AppDelegate {
|
||||
public:
|
||||
AppDelegate() = default;
|
||||
virtual ~AppDelegate() = default;
|
||||
|
||||
LG::Size preferred_desktop_window_size() const override { return LG::Size(320, 400); }
|
||||
|
||||
bool application() override
|
||||
{
|
||||
auto& window = std::xos::construct<AppListWindow>(window_size());
|
||||
window.set_bitmap_format(LG::PixelBitmapFormat::RGBA);
|
||||
auto& dock_view = window.create_superview<AppListView, AppListViewController>();
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
};
|
||||
|
||||
SET_APP_DELEGATE(AppDelegate);
|
||||
27
userland/system/applist/AppEntity.h
Normal file
27
userland/system/applist/AppEntity.h
Normal file
@@ -0,0 +1,27 @@
|
||||
#pragma once
|
||||
#include <libg/PixelBitmap.h>
|
||||
#include <list>
|
||||
#include <string>
|
||||
|
||||
class AppEntity {
|
||||
public:
|
||||
AppEntity() = default;
|
||||
|
||||
void set_icon(LG::PixelBitmap&& icon) { m_icon = std::move(icon); }
|
||||
const LG::PixelBitmap& icon() const { return m_icon; }
|
||||
|
||||
void set_title(const std::string& title) { m_title = title; }
|
||||
const std::string& title() const { return m_title; }
|
||||
|
||||
void set_path_to_exec(const std::string& path) { m_path_to_exec = path; }
|
||||
const std::string& path_to_exec() const { return m_path_to_exec; }
|
||||
|
||||
void set_bundle_id(const std::string& bid) { m_bundle_id = bid; }
|
||||
const std::string& bundle_id() const { return m_bundle_id; }
|
||||
|
||||
private:
|
||||
LG::PixelBitmap m_icon;
|
||||
std::string m_title {};
|
||||
std::string m_path_to_exec {};
|
||||
std::string m_bundle_id {};
|
||||
};
|
||||
99
userland/system/applist/AppListView.cpp
Normal file
99
userland/system/applist/AppListView.cpp
Normal file
@@ -0,0 +1,99 @@
|
||||
#include "AppListView.h"
|
||||
#include "IconView.h"
|
||||
#include <algorithm>
|
||||
#include <cstdlib>
|
||||
#include <libfoundation/EventLoop.h>
|
||||
#include <libfoundation/KeyboardMapping.h>
|
||||
#include <libg/Color.h>
|
||||
#include <libg/ImageLoaders/PNGLoader.h>
|
||||
#include <libui/App.h>
|
||||
#include <libui/CollectionView.h>
|
||||
#include <libui/Context.h>
|
||||
#include <libui/StackView.h>
|
||||
#include <libui/TextField.h>
|
||||
#include <unistd.h>
|
||||
|
||||
static AppListView* this_view;
|
||||
|
||||
AppListView::AppListView(UI::View* superview, const LG::Rect& frame)
|
||||
: UI::View(superview, frame)
|
||||
{
|
||||
// Not used any more, reinit
|
||||
// auto& dock_stack_view = add_subview<UI::StackView>(bounds());
|
||||
// dock_stack_view.set_spacing(padding());
|
||||
// dock_stack_view.set_background_color(LG::Color::Opaque);
|
||||
// dock_stack_view.set_axis(UI::LayoutConstraints::Axis::Horizontal);
|
||||
// dock_stack_view.set_distribution(UI::StackView::Distribution::EqualCentering);
|
||||
// m_dock_stackview = &dock_stack_view;
|
||||
}
|
||||
|
||||
AppListView::AppListView(UI::View* superview, UI::Window* window, const LG::Rect& frame)
|
||||
: UI::View(superview, window, frame)
|
||||
{
|
||||
// Use manual layouting since layout is really simple here.
|
||||
auto& header = add_subview<UI::View>(LG::Rect(0, 0, bounds().width(), 30 + 2 * padding()));
|
||||
header.set_background_color(LG::Color::White);
|
||||
header.layer().set_corner_mask(LG::CornerMask(8, LG::CornerMask::Masked, LG::CornerMask::NonMasked));
|
||||
|
||||
auto& searchbar = add_subview<UI::TextField>(LG::Rect(padding(), padding(), bounds().width() - padding() * 2, 30));
|
||||
searchbar.layer().set_corner_mask(LG::CornerMask(8));
|
||||
searchbar.set_background_color(LG::Color(0xE7F0FA));
|
||||
searchbar.set_placeholder_text("SEARCH");
|
||||
|
||||
auto& applist_grid_view = add_subview<UI::CollectionView>(LG::Rect(0, 2 * padding() + 30, bounds().width(), bounds().height() - (2 * padding() + 30)));
|
||||
applist_grid_view.set_data_source([this](int id) -> View* {
|
||||
return this->view_streamer(id);
|
||||
});
|
||||
m_applist_grid_view = &applist_grid_view;
|
||||
}
|
||||
|
||||
void AppListView::display(const LG::Rect& rect)
|
||||
{
|
||||
LG::Context ctx = UI::graphics_current_context();
|
||||
ctx.add_clip(rect);
|
||||
|
||||
ctx.set_fill_color(background_color());
|
||||
ctx.fill_rounded(bounds(), layer().corner_mask());
|
||||
|
||||
ctx.set_fill_color(LG::Color(120, 120, 120, 50));
|
||||
ctx.draw_shading(LG::Rect(0, 30 + 2 * padding(), bounds().width(), 4), LG::Shading(LG::Shading::Type::TopToBottom));
|
||||
}
|
||||
|
||||
UI::View* AppListView::view_streamer(int id)
|
||||
{
|
||||
if (id * items_per_row() >= m_app_entities.size()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
size_t calc_padding = (m_applist_grid_view->bounds().width() - 2 * padding() - (icon_view_size() * items_per_row())) / (items_per_row() - 1);
|
||||
UI::StackView& dock_stack_view = m_applist_grid_view->add_subview<UI::StackView>(LG::Rect(padding(), 0, m_applist_grid_view->bounds().width() - 2 * padding(), icon_view_size() + calc_padding));
|
||||
dock_stack_view.set_spacing(calc_padding);
|
||||
dock_stack_view.set_background_color(LG::Color::Opaque);
|
||||
dock_stack_view.set_axis(UI::LayoutConstraints::Axis::Horizontal);
|
||||
dock_stack_view.set_distribution(UI::StackView::Distribution::Standard);
|
||||
|
||||
size_t rem = std::min(items_per_row(), (int)m_app_entities.size() - id * items_per_row());
|
||||
for (int i = 0; i < rem; i++) {
|
||||
auto& icon_view = dock_stack_view.add_arranged_subview<IconView>();
|
||||
icon_view.set_title(m_app_entities[id * items_per_row() + i].title());
|
||||
icon_view.entity() = m_app_entities[id * items_per_row() + i];
|
||||
icon_view.add_constraint(UI::Constraint(icon_view, UI::Constraint::Attribute::Height, UI::Constraint::Relation::Equal, icon_view_size()));
|
||||
icon_view.add_constraint(UI::Constraint(icon_view, UI::Constraint::Attribute::Width, UI::Constraint::Relation::Equal, icon_view_size()));
|
||||
}
|
||||
|
||||
return &dock_stack_view;
|
||||
}
|
||||
|
||||
void AppListView::register_entity(const AppEntity& ent)
|
||||
{
|
||||
m_app_entities.push_back(ent);
|
||||
m_applist_grid_view->reload_data();
|
||||
m_applist_grid_view->invalidate_row((m_app_entities.size() - 1) / items_per_row());
|
||||
}
|
||||
|
||||
void AppListView::register_entity(AppEntity&& ent)
|
||||
{
|
||||
m_app_entities.push_back(std::move(ent));
|
||||
m_applist_grid_view->reload_data();
|
||||
m_applist_grid_view->invalidate_row((m_app_entities.size() - 1) / items_per_row());
|
||||
}
|
||||
32
userland/system/applist/AppListView.h
Normal file
32
userland/system/applist/AppListView.h
Normal file
@@ -0,0 +1,32 @@
|
||||
#pragma once
|
||||
#include "AppEntity.h"
|
||||
#include "IconView.h"
|
||||
#include <libg/Font.h>
|
||||
#include <libui/CollectionView.h>
|
||||
#include <libui/View.h>
|
||||
#include <list>
|
||||
#include <string>
|
||||
|
||||
class AppListView : public UI::View {
|
||||
UI_OBJECT();
|
||||
|
||||
public:
|
||||
AppListView(UI::View* superview, const LG::Rect& frame);
|
||||
AppListView(UI::View* superview, UI::Window* window, const LG::Rect& frame);
|
||||
|
||||
static constexpr size_t padding() { return 16; }
|
||||
static constexpr int icon_size() { return 32; }
|
||||
static constexpr int icon_view_size() { return (int)64; }
|
||||
static constexpr int items_per_row() { return (int)4; }
|
||||
|
||||
void display(const LG::Rect& rect) override;
|
||||
|
||||
void register_entity(const AppEntity& ent);
|
||||
void register_entity(AppEntity&& ent);
|
||||
|
||||
private:
|
||||
UI::View* view_streamer(int id);
|
||||
|
||||
UI::CollectionView* m_applist_grid_view {};
|
||||
std::vector<AppEntity> m_app_entities {};
|
||||
};
|
||||
82
userland/system/applist/AppListViewController.h
Normal file
82
userland/system/applist/AppListViewController.h
Normal file
@@ -0,0 +1,82 @@
|
||||
#pragma once
|
||||
#include "AppListView.h"
|
||||
#include <libfoundation/FileManager.h>
|
||||
#include <libfoundation/json/Parser.h>
|
||||
#include <libg/ImageLoaders/PNGLoader.h>
|
||||
#include <libui/App.h>
|
||||
#include <libui/Button.h>
|
||||
#include <libui/Label.h>
|
||||
#include <libui/View.h>
|
||||
#include <libui/ViewController.h>
|
||||
#include <libui/Window.h>
|
||||
#include <memory>
|
||||
#include <sys/types.h>
|
||||
|
||||
char contentdir[256];
|
||||
|
||||
class AppListViewController : public UI::ViewController<AppListView> {
|
||||
public:
|
||||
AppListViewController(AppListView& view)
|
||||
: UI::ViewController<AppListView>(view)
|
||||
{
|
||||
}
|
||||
virtual ~AppListViewController() = default;
|
||||
|
||||
void load_application(const std::string& content_dir)
|
||||
{
|
||||
auto json_parser = LFoundation::Json::Parser(content_dir + "info.json");
|
||||
LFoundation::Json::Object* jobj_root = json_parser.object();
|
||||
if (jobj_root->invalid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto* jdict_root = jobj_root->cast_to<LFoundation::Json::DictObject>();
|
||||
const std::string& bundle_id = jdict_root->data()["bundle_id"]->cast_to<LFoundation::Json::StringObject>()->data();
|
||||
|
||||
AppEntity new_ent;
|
||||
LG::PNG::PNGLoader loader;
|
||||
|
||||
std::string icon_path = jdict_root->data()["icon_path"]->cast_to<LFoundation::Json::StringObject>()->data();
|
||||
new_ent.set_icon(loader.load_from_file(icon_path + "/32x32.png"));
|
||||
|
||||
std::string rel_exec_path = jdict_root->data()["exec_rel_path"]->cast_to<LFoundation::Json::StringObject>()->data();
|
||||
new_ent.set_path_to_exec(content_dir + rel_exec_path);
|
||||
|
||||
new_ent.set_title(jdict_root->data()["name"]->cast_to<LFoundation::Json::StringObject>()->data());
|
||||
new_ent.set_bundle_id(jdict_root->data()["bundle_id"]->cast_to<LFoundation::Json::StringObject>()->data());
|
||||
view().register_entity(std::move(new_ent));
|
||||
|
||||
delete jdict_root;
|
||||
}
|
||||
|
||||
void load_application_list()
|
||||
{
|
||||
auto local_fm = LFoundation::FileManager();
|
||||
local_fm.foreach_object("/Applications", [this](const char* name) {
|
||||
sprintf(contentdir, "/Applications/%s/Content/", name);
|
||||
load_application(contentdir);
|
||||
});
|
||||
}
|
||||
|
||||
virtual void view_did_load() override
|
||||
{
|
||||
view().set_background_color(LG::Color::LightSystemOpaque);
|
||||
view().layer().set_corner_mask(LG::CornerMask(4, LG::CornerMask::Masked, LG::CornerMask::NonMasked));
|
||||
load_application_list();
|
||||
#if 0
|
||||
for (int i = 0; i < 32; i++) {
|
||||
AppEntity new_ent;
|
||||
LG::PNG::PNGLoader loader;
|
||||
|
||||
new_ent.set_icon(loader.load_from_file("/res/icons/apps/about.icon/32x32.png"));
|
||||
new_ent.set_path_to_exec("/Applications/about.app/Content/about");
|
||||
new_ent.set_title("TestApp");
|
||||
new_ent.set_bundle_id("com.x.test");
|
||||
view().register_entity(std::move(new_ent));
|
||||
}
|
||||
#endif
|
||||
view().set_needs_display();
|
||||
}
|
||||
|
||||
private:
|
||||
};
|
||||
7
userland/system/applist/AppListWindow.cpp
Normal file
7
userland/system/applist/AppListWindow.cpp
Normal file
@@ -0,0 +1,7 @@
|
||||
#include "AppListWindow.h"
|
||||
#include "AppListView.h"
|
||||
|
||||
void AppListWindow::receive_event(std::unique_ptr<LFoundation::Event> event)
|
||||
{
|
||||
Window::receive_event(std::move(event));
|
||||
}
|
||||
15
userland/system/applist/AppListWindow.h
Normal file
15
userland/system/applist/AppListWindow.h
Normal file
@@ -0,0 +1,15 @@
|
||||
#pragma once
|
||||
|
||||
#include <libg/Size.h>
|
||||
#include <libui/Screen.h>
|
||||
#include <libui/Window.h>
|
||||
|
||||
class AppListWindow : public UI::Window {
|
||||
public:
|
||||
AppListWindow(const LG::Size& size)
|
||||
: UI::Window("AppList", size, UI::WindowType::AppList)
|
||||
{
|
||||
}
|
||||
|
||||
void receive_event(std::unique_ptr<LFoundation::Event> event) override;
|
||||
};
|
||||
18
userland/system/applist/BUILD.gn
Normal file
18
userland/system/applist/BUILD.gn
Normal file
@@ -0,0 +1,18 @@
|
||||
import("//build/userland/TEMPLATE.gni")
|
||||
|
||||
xOS_executable("applist") {
|
||||
install_path = "System/"
|
||||
sources = [
|
||||
"AppDelegate.cpp",
|
||||
"AppListView.cpp",
|
||||
"AppListWindow.cpp",
|
||||
"IconView.cpp",
|
||||
]
|
||||
configs = [ "//build/userland:userland_flags" ]
|
||||
deplibs = [
|
||||
"libcxx",
|
||||
"libfoundation",
|
||||
"libg",
|
||||
"libui",
|
||||
]
|
||||
}
|
||||
38
userland/system/applist/IconView.cpp
Normal file
38
userland/system/applist/IconView.cpp
Normal file
@@ -0,0 +1,38 @@
|
||||
#include "IconView.h"
|
||||
#include "AppListView.h"
|
||||
#include <libui/App.h>
|
||||
#include <libui/Context.h>
|
||||
#include <libui/Label.h>
|
||||
#include <libui/Screen.h>
|
||||
#include <libui/Window.h>
|
||||
|
||||
IconView::IconView(View* superview, const LG::Rect& frame)
|
||||
: View(superview, frame)
|
||||
{
|
||||
m_label = &add_subview<UI::Label>(LG::Rect(0, AppListView::icon_view_size() - 12, AppListView::icon_view_size(), 12));
|
||||
m_label->set_alignment(UI::Text::Alignment::Center);
|
||||
}
|
||||
|
||||
void IconView::display(const LG::Rect& rect)
|
||||
{
|
||||
const int padding = 4;
|
||||
const int offset_x = (AppListView::icon_view_size() - AppListView::icon_size()) / 2;
|
||||
const int offset_y = (AppListView::icon_view_size() - AppListView::icon_size()) / 2;
|
||||
LG::Context ctx = UI::graphics_current_context();
|
||||
ctx.add_clip(rect);
|
||||
|
||||
auto icon_rect = LG::Rect(offset_x, offset_y, AppListView::icon_size(), AppListView::icon_size());
|
||||
ctx.draw(icon_rect.origin(), m_launch_entity.icon());
|
||||
if (is_hovered()) {
|
||||
ctx.set_fill_color(LG::Color::LightSystemOpaque128);
|
||||
ctx.fill(icon_rect);
|
||||
}
|
||||
|
||||
ctx.set_fill_color(LG::Color(120, 129, 133, 40));
|
||||
ctx.draw_box_shading(icon_rect, LG::Shading(LG::Shading::Type::Box, 0, 3), LG::CornerMask(6));
|
||||
}
|
||||
|
||||
void IconView::on_click()
|
||||
{
|
||||
launch();
|
||||
}
|
||||
60
userland/system/applist/IconView.h
Normal file
60
userland/system/applist/IconView.h
Normal file
@@ -0,0 +1,60 @@
|
||||
#pragma once
|
||||
#include "AppEntity.h"
|
||||
#include <libg/Font.h>
|
||||
#include <libui/Label.h>
|
||||
#include <libui/PopupMenu.h>
|
||||
#include <libui/View.h>
|
||||
#include <list>
|
||||
#include <string>
|
||||
#include <unistd.h>
|
||||
|
||||
class IconView : public UI::View {
|
||||
UI_OBJECT();
|
||||
|
||||
public:
|
||||
IconView(View* superview, const LG::Rect& frame);
|
||||
|
||||
void display(const LG::Rect& rect) override;
|
||||
void mouse_up() override { on_click(); }
|
||||
|
||||
static constexpr size_t underline_height() { return 4; }
|
||||
|
||||
void set_title(const std::string& title)
|
||||
{
|
||||
if (!m_label) {
|
||||
return;
|
||||
}
|
||||
m_label->set_text(title);
|
||||
}
|
||||
|
||||
AppEntity& entity() { return m_launch_entity; }
|
||||
const AppEntity& entity() const { return m_launch_entity; }
|
||||
|
||||
virtual void mouse_entered(const LG::Point<int>& location) override
|
||||
{
|
||||
View::mouse_entered(location);
|
||||
set_needs_display();
|
||||
}
|
||||
|
||||
virtual void mouse_exited() override
|
||||
{
|
||||
View::mouse_exited();
|
||||
set_needs_display();
|
||||
}
|
||||
|
||||
private:
|
||||
void on_click();
|
||||
void launch()
|
||||
{
|
||||
if (fork() == 0) {
|
||||
for (int i = 3; i < 32; i++) {
|
||||
close(i);
|
||||
}
|
||||
execlp(m_launch_entity.path_to_exec().c_str(), m_launch_entity.path_to_exec().c_str(), NULL);
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
|
||||
UI::Label* m_label;
|
||||
AppEntity m_launch_entity;
|
||||
};
|
||||
5
userland/system/dock/.info.mk
Normal file
5
userland/system/dock/.info.mk
Normal file
@@ -0,0 +1,5 @@
|
||||
APPS += DOCK
|
||||
|
||||
DOCK_NAME = dock
|
||||
DOCK_LIBS = cxx ui
|
||||
DOCK_INSTALL_PATH = bin/
|
||||
24
userland/system/dock/AppDelegate.cpp
Normal file
24
userland/system/dock/AppDelegate.cpp
Normal file
@@ -0,0 +1,24 @@
|
||||
#include "DockView.h"
|
||||
#include "DockViewController.h"
|
||||
#include "DockWindow.h"
|
||||
#include <libui/AppDelegate.h>
|
||||
|
||||
class AppDelegate : public UI::AppDelegate {
|
||||
public:
|
||||
AppDelegate() = default;
|
||||
virtual ~AppDelegate() = default;
|
||||
|
||||
LG::Size preferred_desktop_window_size() const override { return LG::Size(400, 300); }
|
||||
|
||||
bool application() override
|
||||
{
|
||||
auto& window = std::xos::construct<DockWindow>();
|
||||
window.set_bitmap_format(LG::PixelBitmapFormat::RGBA);
|
||||
auto& dock_view = window.create_superview<DockView, DockViewController>();
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
};
|
||||
|
||||
SET_APP_DELEGATE(AppDelegate);
|
||||
44
userland/system/dock/AppListView.cpp
Normal file
44
userland/system/dock/AppListView.cpp
Normal file
@@ -0,0 +1,44 @@
|
||||
#include "AppListView.h"
|
||||
#include "DockView.h"
|
||||
#include <libg/ImageLoaders/PNGLoader.h>
|
||||
#include <libui/App.h>
|
||||
#include <libui/Context.h>
|
||||
#include <libui/Label.h>
|
||||
#include <libui/Screen.h>
|
||||
#include <libui/Window.h>
|
||||
|
||||
AppListView::AppListView(View* superview, const LG::Rect& frame)
|
||||
: View(superview, frame)
|
||||
{
|
||||
LG::PNG::PNGLoader loader;
|
||||
m_icon = loader.load_from_file("/res/system/app_list_32.png");
|
||||
}
|
||||
|
||||
void AppListView::display(const LG::Rect& rect)
|
||||
{
|
||||
const int padding = 4;
|
||||
const int offset_x = (DockView::icon_view_size() - m_icon.width()) / 2;
|
||||
const int offset_y = (DockView::icon_view_size() - m_icon.height()) / 2;
|
||||
LG::Context ctx = UI::graphics_current_context();
|
||||
ctx.add_clip(rect);
|
||||
|
||||
auto icon_rect = LG::Rect(offset_x, offset_y, DockView::icon_size(), DockView::icon_size());
|
||||
ctx.draw(icon_rect.origin(), m_icon);
|
||||
if (is_hovered()) {
|
||||
ctx.set_fill_color(LG::Color::LightSystemOpaque128);
|
||||
ctx.fill(icon_rect);
|
||||
}
|
||||
}
|
||||
|
||||
void AppListView::on_click()
|
||||
{
|
||||
int this_window_id = window()->id();
|
||||
|
||||
if (m_target_window_id == INVALID) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto& app = UI::App::the();
|
||||
AskBringToFrontMessage msg(app.connection().key(), this_window_id, m_target_window_id);
|
||||
app.connection().send_async_message(msg);
|
||||
}
|
||||
41
userland/system/dock/AppListView.h
Normal file
41
userland/system/dock/AppListView.h
Normal file
@@ -0,0 +1,41 @@
|
||||
#pragma once
|
||||
#include "DockEntity.h"
|
||||
#include "WindowEntity.h"
|
||||
#include <libg/Font.h>
|
||||
#include <libui/Label.h>
|
||||
#include <libui/PopupMenu.h>
|
||||
#include <libui/View.h>
|
||||
#include <list>
|
||||
#include <string>
|
||||
#include <unistd.h>
|
||||
|
||||
class AppListView : public UI::View {
|
||||
UI_OBJECT();
|
||||
|
||||
static constexpr int INVALID = -1;
|
||||
|
||||
public:
|
||||
AppListView(View* superview, const LG::Rect& frame);
|
||||
|
||||
void set_target_window_id(int winid) { m_target_window_id = winid; }
|
||||
|
||||
void display(const LG::Rect& rect) override;
|
||||
void mouse_up() override { on_click(); }
|
||||
virtual void mouse_entered(const LG::Point<int>& location) override
|
||||
{
|
||||
View::mouse_entered(location);
|
||||
set_needs_display();
|
||||
}
|
||||
|
||||
virtual void mouse_exited() override
|
||||
{
|
||||
View::mouse_exited();
|
||||
set_needs_display();
|
||||
}
|
||||
|
||||
private:
|
||||
void on_click();
|
||||
|
||||
int m_target_window_id { INVALID };
|
||||
LG::PixelBitmap m_icon;
|
||||
};
|
||||
19
userland/system/dock/BUILD.gn
Normal file
19
userland/system/dock/BUILD.gn
Normal file
@@ -0,0 +1,19 @@
|
||||
import("//build/userland/TEMPLATE.gni")
|
||||
|
||||
xOS_executable("dock") {
|
||||
install_path = "System/"
|
||||
sources = [
|
||||
"AppDelegate.cpp",
|
||||
"AppListView.cpp",
|
||||
"DockView.cpp",
|
||||
"DockWindow.cpp",
|
||||
"IconView.cpp",
|
||||
]
|
||||
configs = [ "//build/userland:userland_flags" ]
|
||||
deplibs = [
|
||||
"libcxx",
|
||||
"libfoundation",
|
||||
"libg",
|
||||
"libui",
|
||||
]
|
||||
}
|
||||
29
userland/system/dock/DockEntity.h
Normal file
29
userland/system/dock/DockEntity.h
Normal file
@@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
#include "WindowEntity.h"
|
||||
#include <libg/PixelBitmap.h>
|
||||
#include <list>
|
||||
#include <string>
|
||||
|
||||
class DockEntity {
|
||||
public:
|
||||
DockEntity() = default;
|
||||
|
||||
void set_icon(LG::PixelBitmap&& icon) { m_icon = std::move(icon); }
|
||||
const LG::PixelBitmap& icon() const { return m_icon; }
|
||||
|
||||
void set_path_to_exec(const std::string& path) { m_path_to_exec = path; }
|
||||
const std::string& path_to_exec() const { return m_path_to_exec; }
|
||||
|
||||
void set_bundle_id(const std::string& bid) { m_bundle_id = bid; }
|
||||
const std::string& bundle_id() const { return m_bundle_id; }
|
||||
|
||||
void add_window(const WindowEntity& went) { m_windows.push_front(went); }
|
||||
const std::list<WindowEntity>& windows() const { return m_windows; }
|
||||
std::list<WindowEntity>& windows() { return m_windows; }
|
||||
|
||||
private:
|
||||
LG::PixelBitmap m_icon;
|
||||
std::string m_path_to_exec {};
|
||||
std::string m_bundle_id {};
|
||||
std::list<WindowEntity> m_windows {};
|
||||
};
|
||||
162
userland/system/dock/DockView.cpp
Normal file
162
userland/system/dock/DockView.cpp
Normal file
@@ -0,0 +1,162 @@
|
||||
#include "DockView.h"
|
||||
#include "AppListView.h"
|
||||
#include "IconView.h"
|
||||
#include <algorithm>
|
||||
#include <cstdlib>
|
||||
#include <libfoundation/EventLoop.h>
|
||||
#include <libfoundation/KeyboardMapping.h>
|
||||
#include <libg/Color.h>
|
||||
#include <libg/ImageLoaders/PNGLoader.h>
|
||||
#include <libui/App.h>
|
||||
#include <libui/Context.h>
|
||||
#include <unistd.h>
|
||||
|
||||
static DockView* this_view;
|
||||
|
||||
DockView::DockView(UI::View* superview, const LG::Rect& frame)
|
||||
: UI::View(superview, frame)
|
||||
{
|
||||
init_fill_bounds();
|
||||
init_subviews();
|
||||
}
|
||||
|
||||
DockView::DockView(UI::View* superview, UI::Window* window, const LG::Rect& frame)
|
||||
: UI::View(superview, window, frame)
|
||||
{
|
||||
init_fill_bounds();
|
||||
init_subviews();
|
||||
}
|
||||
|
||||
void DockView::init_fill_bounds()
|
||||
{
|
||||
m_fill_bounds = bounds();
|
||||
m_fill_bounds.set_x(bounds().mid_x());
|
||||
m_fill_bounds.set_width(0);
|
||||
fill_bounds_expand(2 * padding());
|
||||
}
|
||||
|
||||
void DockView::init_subviews()
|
||||
{
|
||||
auto& dock_stack_view = add_subview<UI::StackView>(bounds());
|
||||
dock_stack_view.set_spacing(padding());
|
||||
dock_stack_view.set_background_color(LG::Color::Opaque);
|
||||
dock_stack_view.set_axis(UI::LayoutConstraints::Axis::Horizontal);
|
||||
dock_stack_view.set_distribution(UI::StackView::Distribution::EqualCentering);
|
||||
m_dock_stackview = &dock_stack_view;
|
||||
add_system_buttons();
|
||||
}
|
||||
|
||||
void DockView::add_system_buttons()
|
||||
{
|
||||
fill_bounds_expand(icon_view_size() + padding());
|
||||
auto& icon_view = m_dock_stackview->add_arranged_subview<AppListView>();
|
||||
icon_view.add_constraint(UI::Constraint(icon_view, UI::Constraint::Attribute::Height, UI::Constraint::Relation::Equal, icon_view_size()));
|
||||
icon_view.add_constraint(UI::Constraint(icon_view, UI::Constraint::Attribute::Width, UI::Constraint::Relation::Equal, icon_view_size()));
|
||||
m_applist_view = &icon_view;
|
||||
}
|
||||
|
||||
void DockView::display(const LG::Rect& rect)
|
||||
{
|
||||
LG::Context ctx = UI::graphics_current_context();
|
||||
ctx.add_clip(rect);
|
||||
|
||||
ctx.set_fill_color(background_color());
|
||||
ctx.fill_rounded(fill_bounds(), LG::CornerMask(4, LG::CornerMask::Masked, LG::CornerMask::NonMasked));
|
||||
}
|
||||
|
||||
void DockView::new_dock_entity(const std::string& exec_path, const std::string& icon_path, const std::string& bundle_id)
|
||||
{
|
||||
LG::PNG::PNGLoader loader;
|
||||
|
||||
fill_bounds_expand(icon_view_size() + padding());
|
||||
auto& icon_view = m_dock_stackview->add_arranged_subview<IconView>();
|
||||
icon_view.add_constraint(UI::Constraint(icon_view, UI::Constraint::Attribute::Height, UI::Constraint::Relation::Equal, icon_view_size()));
|
||||
icon_view.add_constraint(UI::Constraint(icon_view, UI::Constraint::Attribute::Width, UI::Constraint::Relation::Equal, icon_view_size()));
|
||||
icon_view.entity().set_icon(loader.load_from_file(icon_path + "/32x32.png"));
|
||||
icon_view.entity().set_path_to_exec(std::move(exec_path));
|
||||
icon_view.entity().set_bundle_id(std::move(bundle_id));
|
||||
m_icon_views.push_back(&icon_view);
|
||||
set_needs_layout();
|
||||
}
|
||||
|
||||
WindowEntity* DockView::find_window_entry(int window_id)
|
||||
{
|
||||
for (auto* view : m_icon_views) {
|
||||
for (auto& wins : view->entity().windows()) {
|
||||
if (wins.window_id() == window_id) {
|
||||
return &wins;
|
||||
}
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void DockView::on_window_create(const std::string& bundle_id, const std::string& icon_path, int window_id, int window_type)
|
||||
{
|
||||
// Don't add an icon of dock (self).
|
||||
if (window()->id() == window_id) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (window_type == WindowType::AppList) {
|
||||
if (m_applist_view) {
|
||||
m_applist_view->set_target_window_id(window_id);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto* view : m_icon_views) {
|
||||
if (view->entity().bundle_id() == bundle_id) {
|
||||
view->entity().add_window(WindowEntity(window_id));
|
||||
set_needs_display();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
new_dock_entity("", icon_path, bundle_id);
|
||||
for (auto* view : m_icon_views) {
|
||||
if (view->entity().bundle_id() == bundle_id) {
|
||||
view->entity().add_window(WindowEntity(window_id));
|
||||
set_needs_display();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DockView::on_window_minimize(int window_id)
|
||||
{
|
||||
auto* ent = find_window_entry(window_id);
|
||||
if (!ent) {
|
||||
return;
|
||||
}
|
||||
ent->set_minimized(true);
|
||||
set_needs_display();
|
||||
}
|
||||
|
||||
void DockView::on_window_remove(int window_id)
|
||||
{
|
||||
// TODO: Currently icons are not properly deleted.
|
||||
for (auto* view : m_icon_views) {
|
||||
for (auto& wins : view->entity().windows()) {
|
||||
if (wins.window_id() == window_id) {
|
||||
auto& win = view->entity().windows();
|
||||
win.erase(std::find(win.begin(), win.end(), WindowEntity(window_id)));
|
||||
set_needs_display();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DockView::set_icon(int window_id, const std::string& path)
|
||||
{
|
||||
}
|
||||
|
||||
void DockView::set_title(int window_id, const std::string& title)
|
||||
{
|
||||
auto* ent = find_window_entry(window_id);
|
||||
if (!ent) {
|
||||
return;
|
||||
}
|
||||
ent->set_title(title);
|
||||
}
|
||||
49
userland/system/dock/DockView.h
Normal file
49
userland/system/dock/DockView.h
Normal file
@@ -0,0 +1,49 @@
|
||||
#pragma once
|
||||
#include "AppListView.h"
|
||||
#include "DockEntity.h"
|
||||
#include "IconView.h"
|
||||
#include "WindowEntity.h"
|
||||
#include <libg/Font.h>
|
||||
#include <libui/StackView.h>
|
||||
#include <libui/View.h>
|
||||
#include <list>
|
||||
#include <string>
|
||||
|
||||
class DockView : public UI::View {
|
||||
UI_OBJECT();
|
||||
|
||||
public:
|
||||
DockView(UI::View* superview, const LG::Rect& frame);
|
||||
DockView(UI::View* superview, UI::Window* window, const LG::Rect& frame);
|
||||
|
||||
static constexpr size_t padding() { return 4; }
|
||||
static constexpr size_t dock_view_height() { return 50; }
|
||||
static constexpr int icon_size() { return 32; }
|
||||
static constexpr int icon_view_size() { return (int)dock_view_height(); }
|
||||
|
||||
void display(const LG::Rect& rect) override;
|
||||
|
||||
WindowEntity* find_window_entry(int window_id);
|
||||
void on_window_create(const std::string& bundle_id, const std::string& icon_path, int window_id, int window_type);
|
||||
void on_window_remove(int window_id);
|
||||
void on_window_minimize(int window_id);
|
||||
void set_icon(int window_id, const std::string& path);
|
||||
void set_title(int window_id, const std::string& title);
|
||||
|
||||
void add_system_buttons();
|
||||
|
||||
void new_dock_entity(const std::string& exec_path, const std::string& icon_path, const std::string& bundle_id);
|
||||
|
||||
private:
|
||||
void init_subviews();
|
||||
void init_fill_bounds();
|
||||
void fill_bounds_expand(size_t l) { m_fill_bounds.set_width(m_fill_bounds.width() + l), m_fill_bounds.set_x(m_fill_bounds.min_x() - l / 2); }
|
||||
const LG::Rect& fill_bounds() const { return m_fill_bounds; }
|
||||
|
||||
void launch(const DockEntity& ent);
|
||||
|
||||
LG::Rect m_fill_bounds {};
|
||||
UI::StackView* m_dock_stackview {};
|
||||
AppListView* m_applist_view {};
|
||||
std::list<IconView*> m_icon_views {};
|
||||
};
|
||||
31
userland/system/dock/DockViewController.h
Normal file
31
userland/system/dock/DockViewController.h
Normal file
@@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
#include "DockView.h"
|
||||
#include <libui/App.h>
|
||||
#include <libui/Button.h>
|
||||
#include <libui/Label.h>
|
||||
#include <libui/View.h>
|
||||
#include <libui/ViewController.h>
|
||||
#include <libui/Window.h>
|
||||
#include <memory>
|
||||
#include <sys/types.h>
|
||||
|
||||
class DockViewController : public UI::ViewController<DockView> {
|
||||
public:
|
||||
DockViewController(DockView& view)
|
||||
: UI::ViewController<DockView>(view)
|
||||
{
|
||||
}
|
||||
virtual ~DockViewController() = default;
|
||||
|
||||
virtual void view_did_load() override
|
||||
{
|
||||
view().set_background_color(LG::Color::LightSystemOpaque);
|
||||
view().new_dock_entity("/Applications/about.app/Content/about", "/res/icons/apps/about.icon", "com.x.about");
|
||||
view().new_dock_entity("/Applications/terminal.app/Content/terminal", "/res/icons/apps/terminal.icon", "com.x.terminal");
|
||||
view().new_dock_entity("/Applications/activity_monitor.app/Content/activity_monitor", "/res/icons/apps/activity_monitor.icon", "com.x.activity_monitor");
|
||||
view().new_dock_entity("/Applications/calculator.app/Content/calculator", "/res/icons/apps/calculator.icon", "com.x.calculator");
|
||||
view().set_needs_display();
|
||||
}
|
||||
|
||||
private:
|
||||
};
|
||||
42
userland/system/dock/DockWindow.cpp
Normal file
42
userland/system/dock/DockWindow.cpp
Normal file
@@ -0,0 +1,42 @@
|
||||
#include "DockWindow.h"
|
||||
#include "DockView.h"
|
||||
|
||||
void DockWindow::receive_event(std::unique_ptr<LFoundation::Event> event)
|
||||
{
|
||||
switch (event->type()) {
|
||||
case UI::Event::Type::NotifyWindowCreateEvent: {
|
||||
UI::NotifyWindowCreateEvent& own_event = *(UI::NotifyWindowCreateEvent*)event.get();
|
||||
DockView* it = (DockView*)superview();
|
||||
it->on_window_create(own_event.bundle_id(), own_event.icon_path(), own_event.window_id(), own_event.window_type());
|
||||
break;
|
||||
}
|
||||
|
||||
case UI::Event::Type::NotifyWindowStatusChangedEvent: {
|
||||
UI::NotifyWindowStatusChangedEvent& own_event = *(UI::NotifyWindowStatusChangedEvent*)event.get();
|
||||
DockView* it = (DockView*)superview();
|
||||
if (own_event.type() == UI::WindowStatusUpdateType::Removed) {
|
||||
it->on_window_remove(own_event.changed_window_id());
|
||||
}
|
||||
if (own_event.type() == UI::WindowStatusUpdateType::Minimized) {
|
||||
it->on_window_minimize(own_event.changed_window_id());
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case UI::Event::Type::NotifyWindowIconChangedEvent: {
|
||||
UI::NotifyWindowIconChangedEvent& own_event = *(UI::NotifyWindowIconChangedEvent*)event.get();
|
||||
DockView* it = (DockView*)superview();
|
||||
it->set_icon(own_event.changed_window_id(), own_event.icon_path());
|
||||
break;
|
||||
}
|
||||
|
||||
case UI::Event::Type::NotifyWindowTitleChangedEvent: {
|
||||
UI::NotifyWindowTitleChangedEvent& own_event = *(UI::NotifyWindowTitleChangedEvent*)event.get();
|
||||
DockView* it = (DockView*)superview();
|
||||
it->set_title(own_event.changed_window_id(), own_event.title());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Window::receive_event(std::move(event));
|
||||
}
|
||||
22
userland/system/dock/DockWindow.h
Normal file
22
userland/system/dock/DockWindow.h
Normal file
@@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
|
||||
#include <libg/Size.h>
|
||||
#include <libui/Screen.h>
|
||||
#include <libui/Window.h>
|
||||
|
||||
class DockWindow : public UI::Window {
|
||||
public:
|
||||
DockWindow()
|
||||
: UI::Window("Dock", LG::Size(UI::Screen::main().bounds().width(), 50), UI::WindowType::Homescreen)
|
||||
{
|
||||
if (fork() == 0) {
|
||||
for (int i = 3; i < 32; i++) {
|
||||
close(i);
|
||||
}
|
||||
execlp("/System/applist", "/System/applist", NULL);
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
|
||||
void receive_event(std::unique_ptr<LFoundation::Event> event) override;
|
||||
};
|
||||
74
userland/system/dock/IconView.cpp
Normal file
74
userland/system/dock/IconView.cpp
Normal file
@@ -0,0 +1,74 @@
|
||||
#include "IconView.h"
|
||||
#include "DockView.h"
|
||||
#include <libui/App.h>
|
||||
#include <libui/Context.h>
|
||||
#include <libui/Label.h>
|
||||
#include <libui/Screen.h>
|
||||
#include <libui/Window.h>
|
||||
|
||||
IconView::IconView(View* superview, const LG::Rect& frame)
|
||||
: View(superview, frame)
|
||||
{
|
||||
}
|
||||
|
||||
void IconView::display(const LG::Rect& rect)
|
||||
{
|
||||
const int padding = 4;
|
||||
const int offset_x = (DockView::icon_view_size() - DockView::icon_size()) / 2;
|
||||
const int offset_y = (DockView::icon_view_size() - DockView::icon_size()) / 2;
|
||||
LG::Context ctx = UI::graphics_current_context();
|
||||
ctx.add_clip(rect);
|
||||
|
||||
auto icon_rect = LG::Rect(offset_x, offset_y, DockView::icon_size(), DockView::icon_size());
|
||||
ctx.draw(icon_rect.origin(), m_launch_entity.icon());
|
||||
if (is_hovered()) {
|
||||
ctx.set_fill_color(LG::Color::LightSystemOpaque128);
|
||||
ctx.fill(icon_rect);
|
||||
}
|
||||
|
||||
ctx.set_fill_color(LG::Color(120, 129, 133, 40));
|
||||
ctx.draw_box_shading(icon_rect, LG::Shading(LG::Shading::Type::Box, 0, 3), LG::CornerMask(6));
|
||||
|
||||
ctx.set_fill_color(LG::Color(163, 174, 190));
|
||||
const int underline_y = DockView::icon_view_size() - underline_height() - padding;
|
||||
if (entity().windows().size() > 0) {
|
||||
const int len = 8;
|
||||
ctx.fill({ (DockView::icon_view_size() - len) / 2, underline_y, len, underline_height() });
|
||||
}
|
||||
}
|
||||
|
||||
void IconView::on_click()
|
||||
{
|
||||
if (entity().windows().empty()) {
|
||||
launch();
|
||||
return;
|
||||
} else {
|
||||
auto demo_menu = UI::Menu();
|
||||
for (auto& win : entity().windows()) {
|
||||
auto title = win.title();
|
||||
int this_window_id = window()->id();
|
||||
int target_window_id = win.window_id();
|
||||
|
||||
if (win.is_minimized()) {
|
||||
title += " - minimized";
|
||||
}
|
||||
|
||||
demo_menu.add_item(UI::MenuItem(title, [this, this_window_id, target_window_id] {
|
||||
for (auto& win : entity().windows()) {
|
||||
if (win.window_id() == target_window_id) {
|
||||
win.set_minimized(false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
auto& app = UI::App::the();
|
||||
AskBringToFrontMessage msg(app.connection().key(), this_window_id, target_window_id);
|
||||
app.connection().send_async_message(msg);
|
||||
}));
|
||||
}
|
||||
demo_menu.add_item(UI::MenuItem("New window", [this] {
|
||||
launch();
|
||||
}));
|
||||
window()->popup_manager().show({ frame().min_x(), (int)UI::Screen::main().bounds().height() - (int)DockView::dock_view_height() - 4 }, demo_menu);
|
||||
}
|
||||
}
|
||||
61
userland/system/dock/IconView.h
Normal file
61
userland/system/dock/IconView.h
Normal file
@@ -0,0 +1,61 @@
|
||||
#pragma once
|
||||
#include "DockEntity.h"
|
||||
#include "WindowEntity.h"
|
||||
#include <libg/Font.h>
|
||||
#include <libui/Label.h>
|
||||
#include <libui/PopupMenu.h>
|
||||
#include <libui/View.h>
|
||||
#include <list>
|
||||
#include <string>
|
||||
#include <unistd.h>
|
||||
|
||||
class IconView : public UI::View {
|
||||
UI_OBJECT();
|
||||
|
||||
public:
|
||||
IconView(View* superview, const LG::Rect& frame);
|
||||
|
||||
void display(const LG::Rect& rect) override;
|
||||
void mouse_up() override { on_click(); }
|
||||
|
||||
static constexpr size_t underline_height() { return 2; }
|
||||
|
||||
void set_title(const std::string& title)
|
||||
{
|
||||
if (!m_label) {
|
||||
return;
|
||||
}
|
||||
m_label->set_text(title);
|
||||
}
|
||||
|
||||
DockEntity& entity() { return m_launch_entity; }
|
||||
const DockEntity& entity() const { return m_launch_entity; }
|
||||
|
||||
virtual void mouse_entered(const LG::Point<int>& location) override
|
||||
{
|
||||
View::mouse_entered(location);
|
||||
set_needs_display();
|
||||
}
|
||||
|
||||
virtual void mouse_exited() override
|
||||
{
|
||||
View::mouse_exited();
|
||||
set_needs_display();
|
||||
}
|
||||
|
||||
private:
|
||||
void on_click();
|
||||
void launch()
|
||||
{
|
||||
if (fork() == 0) {
|
||||
for (int i = 3; i < 32; i++) {
|
||||
close(i);
|
||||
}
|
||||
execlp(m_launch_entity.path_to_exec().c_str(), m_launch_entity.path_to_exec().c_str(), NULL);
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
|
||||
UI::Label* m_label;
|
||||
DockEntity m_launch_entity;
|
||||
};
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user