emoji panel demo step01
This commit is contained in:
1 parent
4f62123699
commit
604cf987b1
11 files changed
+529
-2
No files matched your search
@@ -66,6 +66,7 @@ target_include_directories(msimeui
|
||||
target_link_libraries(msimeui PUBLIC
|
||||
d2d1.lib
|
||||
dwrite.lib
|
||||
dwmapi.lib
|
||||
imm32.lib
|
||||
ole32.lib
|
||||
oleaut32.lib
|
||||
@@ -84,7 +85,18 @@ add_executable(msimeui-demo WIN32
|
||||
target_link_libraries(msimeui-demo PRIVATE msimeui)
|
||||
target_compile_definitions(msimeui-demo PRIVATE MSIMEUI_ASSET_DIR=L\"${CMAKE_CURRENT_SOURCE_DIR}/assets\")
|
||||
|
||||
add_executable(msimeui-emoji-panel WIN32
|
||||
demos/msimeui-emoji-panel/EmojiPanel.cpp
|
||||
demos/msimeui-emoji-panel/EmojiPanel.h
|
||||
demos/msimeui-emoji-panel/main.cpp
|
||||
demos/msimeui-emoji-panel/msimeui-emoji-panel.rc
|
||||
demos/msimeui-emoji-panel/resource.h
|
||||
)
|
||||
|
||||
target_link_libraries(msimeui-emoji-panel PRIVATE msimeui)
|
||||
|
||||
if(MSVC)
|
||||
target_compile_options(msimeui PRIVATE /W4 /permissive-)
|
||||
target_compile_options(msimeui-demo PRIVATE /W4 /permissive-)
|
||||
target_compile_options(msimeui-emoji-panel PRIVATE /W4 /permissive-)
|
||||
endif()
|
||||
@@ -191,6 +191,21 @@ cmake --build build --config Debug
|
||||
./scripts/launch-demo.ps1 -Configuration Release
|
||||
```
|
||||
|
||||
### 独立 Emoji 面板
|
||||
|
||||
项目包含独立的 `msimeui-emoji-panel.exe`,用于预览接近微软输入法风格的深色表情面板:
|
||||
|
||||
```powershell
|
||||
./scripts/build-emoji-panel.ps1
|
||||
./scripts/launch-emoji-panel.ps1
|
||||
```
|
||||
|
||||
也可以一次完成构建和启动:
|
||||
|
||||
```powershell
|
||||
./scripts/launch-emoji-panel.ps1 -Build
|
||||
```
|
||||
|
||||
如果切换过 CMake generator、Visual Studio 版本或者 preset,可以加 `-Fresh` 重新生成构建目录:
|
||||
|
||||
```powershell
|
||||
@@ -202,6 +217,7 @@ cmake --build build --config Debug
|
||||
|
||||
```text
|
||||
build/bin/Debug/msimeui-demo.exe
|
||||
build/bin/Debug/msimeui-emoji-panel.exe
|
||||
```
|
||||
|
||||
## 目录
|
||||
@@ -209,6 +225,7 @@ build/bin/Debug/msimeui-demo.exe
|
||||
- `include/msimeui`:新框架公开头文件
|
||||
- `src`:框架实现
|
||||
- `demos/msimeui-demo`:demo 入口和示例场景
|
||||
- `demos/msimeui-emoji-panel`:独立 Emoji 面板入口和专用控件
|
||||
- `src/tsf`:内置 TSF 文本输入实现
|
||||
|
||||
## 后续工作
|
||||
|
||||
@@ -0,0 +1,289 @@
|
||||
#include "EmojiPanel.h"
|
||||
|
||||
#include "msimeui/DeviceResources.h"
|
||||
#include "msimeui/Window.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
namespace msimeui
|
||||
{
|
||||
namespace
|
||||
{
|
||||
constexpr float kHeaderHeight = 58.0f;
|
||||
constexpr float kCategoryTop = 66.0f;
|
||||
constexpr float kCategoryHeight = 58.0f;
|
||||
constexpr float kSearchTop = 142.0f;
|
||||
constexpr float kSearchHeight = 48.0f;
|
||||
constexpr float kContentTop = 214.0f;
|
||||
constexpr float kCellSize = 64.0f;
|
||||
constexpr float kGridLeft = 26.0f;
|
||||
constexpr size_t kColumns = 7;
|
||||
|
||||
bool Contains(const RectF &rect, const PointF &point)
|
||||
{
|
||||
return point.x >= rect.x && point.x <= rect.x + rect.width && point.y >= rect.y && point.y <= rect.y + rect.height;
|
||||
}
|
||||
|
||||
void FillRect(DeviceResources &resources, const RectF &rect, const D2D1_COLOR_F &color, float radius = 0.0f)
|
||||
{
|
||||
auto *target = resources.GetRenderTarget();
|
||||
auto *brush = resources.GetSolidColorBrush(color);
|
||||
if (!target || !brush)
|
||||
{
|
||||
return;
|
||||
}
|
||||
const auto d2dRect = D2D1::RectF(rect.x, rect.y, rect.x + rect.width, rect.y + rect.height);
|
||||
if (radius > 0.0f)
|
||||
{
|
||||
target->FillRoundedRectangle(D2D1::RoundedRect(d2dRect, radius, radius), brush);
|
||||
}
|
||||
else
|
||||
{
|
||||
target->FillRectangle(d2dRect, brush);
|
||||
}
|
||||
}
|
||||
|
||||
void StrokeRect(DeviceResources &resources, const RectF &rect, const D2D1_COLOR_F &color, float radius, float width)
|
||||
{
|
||||
auto *target = resources.GetRenderTarget();
|
||||
auto *brush = resources.GetSolidColorBrush(color);
|
||||
if (target && brush)
|
||||
{
|
||||
target->DrawRoundedRectangle(D2D1::RoundedRect(D2D1::RectF(rect.x, rect.y, rect.x + rect.width,
|
||||
rect.y + rect.height), radius, radius), brush, width);
|
||||
}
|
||||
}
|
||||
|
||||
void DrawText(DeviceResources &resources, const std::wstring &text, const RectF &rect, float size,
|
||||
const D2D1_COLOR_F &color, const wchar_t *font = L"Segoe UI",
|
||||
DWRITE_TEXT_ALIGNMENT alignment = DWRITE_TEXT_ALIGNMENT_LEADING,
|
||||
DWRITE_FONT_WEIGHT weight = DWRITE_FONT_WEIGHT_NORMAL)
|
||||
{
|
||||
auto *target = resources.GetRenderTarget();
|
||||
auto *format = resources.GetTextFormat(font, size, weight, alignment, DWRITE_PARAGRAPH_ALIGNMENT_CENTER,
|
||||
DWRITE_WORD_WRAPPING_NO_WRAP);
|
||||
auto *brush = resources.GetSolidColorBrush(color);
|
||||
if (target && format && brush)
|
||||
{
|
||||
target->DrawTextW(text.c_str(), static_cast<UINT32>(text.size()), format,
|
||||
D2D1::RectF(rect.x, rect.y, rect.x + rect.width, rect.y + rect.height), brush,
|
||||
static_cast<D2D1_DRAW_TEXT_OPTIONS>(D2D1_DRAW_TEXT_OPTIONS_CLIP |
|
||||
D2D1_DRAW_TEXT_OPTIONS_ENABLE_COLOR_FONT));
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
SizeF EmojiPanel::Measure(const SizeF &availableSize)
|
||||
{
|
||||
return availableSize;
|
||||
}
|
||||
|
||||
void EmojiPanel::Arrange(const RectF &finalRect)
|
||||
{
|
||||
bounds_ = finalRect;
|
||||
ClampScroll();
|
||||
}
|
||||
|
||||
RectF EmojiPanel::CloseRect() const
|
||||
{
|
||||
return {bounds_.x + bounds_.width - 52.0f, bounds_.y + 9.0f, 42.0f, 40.0f};
|
||||
}
|
||||
|
||||
float EmojiPanel::ContentHeight() const
|
||||
{
|
||||
float height = 0.0f;
|
||||
for (const auto &group : groups_)
|
||||
{
|
||||
const size_t rows = (group.items.size() + kColumns - 1) / kColumns;
|
||||
height += 42.0f + static_cast<float>(rows) * kCellSize + 22.0f;
|
||||
}
|
||||
return height;
|
||||
}
|
||||
|
||||
void EmojiPanel::ClampScroll()
|
||||
{
|
||||
const float viewportHeight = std::max(bounds_.height - kContentTop, 0.0f);
|
||||
scrollOffset_ = std::clamp(scrollOffset_, 0.0f, std::max(ContentHeight() - viewportHeight, 0.0f));
|
||||
}
|
||||
|
||||
size_t EmojiPanel::HitEmoji(const PointF &point) const
|
||||
{
|
||||
if (point.y < bounds_.y + kContentTop || point.x < bounds_.x + kGridLeft)
|
||||
{
|
||||
return static_cast<size_t>(-1);
|
||||
}
|
||||
float y = bounds_.y + kContentTop - scrollOffset_;
|
||||
size_t flatIndex = 0;
|
||||
for (const auto &group : groups_)
|
||||
{
|
||||
y += 42.0f;
|
||||
for (size_t index = 0; index < group.items.size(); ++index, ++flatIndex)
|
||||
{
|
||||
const RectF cell = {bounds_.x + kGridLeft + static_cast<float>(index % kColumns) * kCellSize,
|
||||
y + static_cast<float>(index / kColumns) * kCellSize, kCellSize - 8.0f,
|
||||
kCellSize - 8.0f};
|
||||
if (Contains(cell, point))
|
||||
{
|
||||
return flatIndex;
|
||||
}
|
||||
}
|
||||
y += static_cast<float>((group.items.size() + kColumns - 1) / kColumns) * kCellSize + 22.0f;
|
||||
}
|
||||
return static_cast<size_t>(-1);
|
||||
}
|
||||
|
||||
void EmojiPanel::Render(DeviceResources &resources)
|
||||
{
|
||||
auto *target = resources.GetRenderTarget();
|
||||
if (!target)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
FillRect(resources, bounds_, D2D1::ColorF(0x202027));
|
||||
DrawText(resources, L"Emoji and more", {bounds_.x + 24.0f, bounds_.y, 240.0f, kHeaderHeight}, 18.0f,
|
||||
D2D1::ColorF(0xF7F7FA), L"Segoe UI", DWRITE_TEXT_ALIGNMENT_LEADING, DWRITE_FONT_WEIGHT_SEMI_BOLD);
|
||||
|
||||
const RectF close = CloseRect();
|
||||
if (closeHovered_ || closePressed_)
|
||||
{
|
||||
FillRect(resources, close, closePressed_ ? D2D1::ColorF(0x4B4B55) : D2D1::ColorF(0x36363F), 7.0f);
|
||||
}
|
||||
DrawText(resources, L"\u00D7", close, 31.0f, D2D1::ColorF(0xF4F4F7), L"Segoe UI",
|
||||
DWRITE_TEXT_ALIGNMENT_CENTER);
|
||||
|
||||
const wchar_t *categories[] = {L"\u2665", L"\u263A", L"GIF", L";-)" , L"\u2605", L"\u25A3"};
|
||||
const float categoryWidths[] = {58.0f, 58.0f, 66.0f, 66.0f, 64.0f, 58.0f};
|
||||
float categoryX = bounds_.x + 22.0f;
|
||||
for (size_t index = 0; index < 6; ++index)
|
||||
{
|
||||
DrawText(resources, categories[index], {categoryX, bounds_.y + kCategoryTop, categoryWidths[index], kCategoryHeight},
|
||||
index == 2 ? 14.0f : 25.0f, D2D1::ColorF(0xF5F5F8), L"Segoe UI Symbol",
|
||||
DWRITE_TEXT_ALIGNMENT_CENTER, index == 2 ? DWRITE_FONT_WEIGHT_SEMI_BOLD : DWRITE_FONT_WEIGHT_NORMAL);
|
||||
categoryX += categoryWidths[index] + 8.0f;
|
||||
}
|
||||
FillRect(resources, {bounds_.x + 37.0f, bounds_.y + 121.0f, 29.0f, 4.0f}, D2D1::ColorF(0xD88BDE), 2.0f);
|
||||
|
||||
const RectF search = {bounds_.x + 24.0f, bounds_.y + kSearchTop, bounds_.width - 48.0f, kSearchHeight};
|
||||
FillRect(resources, search, D2D1::ColorF(0x292930), 7.0f);
|
||||
StrokeRect(resources, search, D2D1::ColorF(0x45454F), 7.0f, 1.0f);
|
||||
DrawText(resources, L"\u2315", {search.x + 13.0f, search.y, 42.0f, search.height}, 29.0f,
|
||||
D2D1::ColorF(0xF2F2F5), L"Segoe UI Symbol", DWRITE_TEXT_ALIGNMENT_CENTER);
|
||||
DrawText(resources, L"Search", {search.x + 60.0f, search.y, search.width - 70.0f, search.height}, 18.0f,
|
||||
D2D1::ColorF(0xC9C9D0));
|
||||
FillRect(resources, {search.x, search.y + search.height - 2.0f, search.width, 2.0f}, D2D1::ColorF(0xD88BDE));
|
||||
|
||||
const RectF viewport = {bounds_.x, bounds_.y + kContentTop, bounds_.width, bounds_.height - kContentTop};
|
||||
target->PushAxisAlignedClip(D2D1::RectF(viewport.x, viewport.y, viewport.x + viewport.width,
|
||||
viewport.y + viewport.height), D2D1_ANTIALIAS_MODE_PER_PRIMITIVE);
|
||||
float y = bounds_.y + kContentTop - scrollOffset_;
|
||||
size_t flatIndex = 0;
|
||||
for (const auto &group : groups_)
|
||||
{
|
||||
DrawText(resources, group.title, {bounds_.x + 24.0f, y, bounds_.width - 48.0f, 42.0f}, 19.0f,
|
||||
D2D1::ColorF(0xF4F4F7), L"Segoe UI", DWRITE_TEXT_ALIGNMENT_LEADING,
|
||||
DWRITE_FONT_WEIGHT_SEMI_BOLD);
|
||||
y += 42.0f;
|
||||
for (size_t index = 0; index < group.items.size(); ++index, ++flatIndex)
|
||||
{
|
||||
const RectF cell = {bounds_.x + kGridLeft + static_cast<float>(index % kColumns) * kCellSize,
|
||||
y + static_cast<float>(index / kColumns) * kCellSize, kCellSize - 8.0f,
|
||||
kCellSize - 8.0f};
|
||||
if (flatIndex == hoveredEmoji_ || flatIndex == pressedEmoji_)
|
||||
{
|
||||
FillRect(resources, cell, flatIndex == pressedEmoji_ ? D2D1::ColorF(0x555560) : D2D1::ColorF(0x3B3B44),
|
||||
6.0f);
|
||||
}
|
||||
DrawText(resources, group.items[index], cell, 30.0f, D2D1::ColorF(0xFFFFFF), L"Segoe UI Emoji",
|
||||
DWRITE_TEXT_ALIGNMENT_CENTER);
|
||||
}
|
||||
y += static_cast<float>((group.items.size() + kColumns - 1) / kColumns) * kCellSize + 22.0f;
|
||||
}
|
||||
target->PopAxisAlignedClip();
|
||||
|
||||
const float viewportHeight = std::max(viewport.height, 1.0f);
|
||||
if (ContentHeight() > viewportHeight)
|
||||
{
|
||||
const float trackHeight = viewportHeight - 12.0f;
|
||||
const float thumbHeight = std::max(trackHeight * viewportHeight / ContentHeight(), 34.0f);
|
||||
const float maxScroll = ContentHeight() - viewportHeight;
|
||||
const float thumbY = viewport.y + 6.0f + (trackHeight - thumbHeight) * (scrollOffset_ / maxScroll);
|
||||
FillRect(resources, {bounds_.x + bounds_.width - 12.0f, thumbY, 6.0f, thumbHeight}, D2D1::ColorF(0xB8B8C0), 3.0f);
|
||||
}
|
||||
}
|
||||
|
||||
bool EmojiPanel::HitTest(const PointF &point) const
|
||||
{
|
||||
return Contains(bounds_, point);
|
||||
}
|
||||
|
||||
bool EmojiPanel::OnMouseDown(const POINT &point, WPARAM)
|
||||
{
|
||||
if (!window_)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
const PointF dip = window_->ClientPixelsToDips(point);
|
||||
closePressed_ = Contains(CloseRect(), dip);
|
||||
pressedEmoji_ = closePressed_ ? static_cast<size_t>(-1) : HitEmoji(dip);
|
||||
InvalidateVisual();
|
||||
return closePressed_ || pressedEmoji_ != static_cast<size_t>(-1);
|
||||
}
|
||||
|
||||
bool EmojiPanel::OnMouseUp(const POINT &point, WPARAM)
|
||||
{
|
||||
if (!window_)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
const PointF dip = window_->ClientPixelsToDips(point);
|
||||
const bool close = closePressed_ && Contains(CloseRect(), dip);
|
||||
closePressed_ = false;
|
||||
pressedEmoji_ = static_cast<size_t>(-1);
|
||||
InvalidateVisual();
|
||||
if (close)
|
||||
{
|
||||
PostMessageW(window_->GetHandle(), WM_CLOSE, 0, 0);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool EmojiPanel::OnMouseMove(const POINT &point, WPARAM)
|
||||
{
|
||||
if (!window_)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
const PointF dip = window_->ClientPixelsToDips(point);
|
||||
const bool closeHover = Contains(CloseRect(), dip);
|
||||
const size_t emojiHover = closeHover ? static_cast<size_t>(-1) : HitEmoji(dip);
|
||||
if (closeHovered_ != closeHover || hoveredEmoji_ != emojiHover)
|
||||
{
|
||||
closeHovered_ = closeHover;
|
||||
hoveredEmoji_ = emojiHover;
|
||||
InvalidateVisual();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void EmojiPanel::OnMouseLeave()
|
||||
{
|
||||
closeHovered_ = false;
|
||||
hoveredEmoji_ = static_cast<size_t>(-1);
|
||||
InvalidateVisual();
|
||||
}
|
||||
|
||||
bool EmojiPanel::OnMouseWheel(const POINT &, short delta, WPARAM)
|
||||
{
|
||||
scrollOffset_ -= static_cast<float>(delta) / WHEEL_DELTA * 72.0f;
|
||||
ClampScroll();
|
||||
InvalidateVisual();
|
||||
return true;
|
||||
}
|
||||
|
||||
HCURSOR EmojiPanel::GetCursor() const
|
||||
{
|
||||
return LoadCursor(nullptr, IDC_ARROW);
|
||||
}
|
||||
} // namespace msimeui
|
||||
@@ -0,0 +1,51 @@
|
||||
#pragma once
|
||||
|
||||
#include "msimeui/Layout.h"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace msimeui
|
||||
{
|
||||
class EmojiPanel final : public Visual
|
||||
{
|
||||
public:
|
||||
SizeF Measure(const SizeF &availableSize) override;
|
||||
void Arrange(const RectF &finalRect) override;
|
||||
void Render(DeviceResources &deviceResources) override;
|
||||
bool HitTest(const PointF &point) const override;
|
||||
bool OnMouseDown(const POINT &point, WPARAM keyState) override;
|
||||
bool OnMouseUp(const POINT &point, WPARAM keyState) override;
|
||||
bool OnMouseMove(const POINT &point, WPARAM keyState) override;
|
||||
void OnMouseLeave() override;
|
||||
bool OnMouseWheel(const POINT &point, short delta, WPARAM keyState) override;
|
||||
HCURSOR GetCursor() const override;
|
||||
|
||||
private:
|
||||
struct Group
|
||||
{
|
||||
std::wstring title;
|
||||
std::vector<std::wstring> items;
|
||||
};
|
||||
|
||||
RectF CloseRect() const;
|
||||
size_t HitEmoji(const PointF &point) const;
|
||||
float ContentHeight() const;
|
||||
void ClampScroll();
|
||||
|
||||
std::vector<Group> groups_ = {
|
||||
{L"Emoji", {L"\U0001F44A", L"\U0001F602", L"\U0001F923", L"\U0001F605", L"\U0001F618", L"\U0001F60D",
|
||||
L"\U0001F44C", L"\U0001F60A", L"\U0001F970", L"\u2764\uFE0F", L"\U0001F495", L"\U0001F62A",
|
||||
L"\U0001F914", L"\U0001F60E", L"\U0001F973", L"\U0001F609", L"\U0001F62D", L"\U0001F621"}},
|
||||
{L"People and body", {L"\U0001F44D", L"\U0001F44F", L"\U0001F64C", L"\U0001F64F", L"\U0001F4AA", L"\U0001F91D",
|
||||
L"\U0001F440", L"\U0001F9E0", L"\U0001F463", L"\U0001F9D1", L"\U0001F468", L"\U0001F469"}},
|
||||
{L"Animals and nature", {L"\U0001F436", L"\U0001F431", L"\U0001F98A", L"\U0001F43C", L"\U0001F42F", L"\U0001F981",
|
||||
L"\U0001F438", L"\U0001F435", L"\U0001F427", L"\U0001F98B", L"\U0001F33B", L"\U0001F335"}},
|
||||
};
|
||||
float scrollOffset_ = 0.0f;
|
||||
size_t hoveredEmoji_ = static_cast<size_t>(-1);
|
||||
size_t pressedEmoji_ = static_cast<size_t>(-1);
|
||||
bool closeHovered_ = false;
|
||||
bool closePressed_ = false;
|
||||
};
|
||||
} // namespace msimeui
|
||||
@@ -0,0 +1,44 @@
|
||||
#include "EmojiPanel.h"
|
||||
|
||||
#include "msimeui/Application.h"
|
||||
#include "msimeui/Scene.h"
|
||||
#include "msimeui/Theme.h"
|
||||
#include "msimeui/Window.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <memory>
|
||||
|
||||
int WINAPI wWinMain(HINSTANCE, HINSTANCE, PWSTR, int nCmdShow)
|
||||
{
|
||||
if (!msimeui::Application::Initialize())
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
msimeui::Theme theme = msimeui::ThemeManager::GetCurrent();
|
||||
theme.windowBackground = D2D1::ColorF(0x202027);
|
||||
theme.surface = D2D1::ColorF(0x202027);
|
||||
theme.textPrimary = D2D1::ColorF(0xF5F5F7);
|
||||
theme.textSecondary = D2D1::ColorF(0xC9C9D0);
|
||||
msimeui::ThemeManager::SetCurrent(std::move(theme));
|
||||
|
||||
const float scale = static_cast<float>(GetDpiForSystem()) / 96.0f;
|
||||
const int width = static_cast<int>(std::lround(550.0f * scale));
|
||||
const int height = static_cast<int>(std::lround(610.0f * scale));
|
||||
msimeui::Window window(L"msimeui.EmojiPanel", L"Emoji and more", width, height);
|
||||
window.SetWindowStyle(WS_POPUP, WS_EX_TOOLWINDOW);
|
||||
window.SetDragRegionHeight(56.0f);
|
||||
window.SetRoundedCorners(true);
|
||||
if (!window.Create())
|
||||
{
|
||||
msimeui::Application::Shutdown();
|
||||
return -1;
|
||||
}
|
||||
|
||||
auto scene = std::make_unique<msimeui::Scene>();
|
||||
scene->SetRoot(std::make_shared<msimeui::EmojiPanel>());
|
||||
window.SetScene(std::move(scene));
|
||||
const int result = window.Run(nCmdShow);
|
||||
msimeui::Application::Shutdown();
|
||||
return result;
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
#include "resource.h"
|
||||
|
||||
IDI_MSIMEUI_ICON ICON "../../assets/msimeui.ico"
|
||||
@@ -0,0 +1,3 @@
|
||||
#pragma once
|
||||
|
||||
#define IDI_MSIMEUI_ICON 101
|
||||
@@ -33,6 +33,9 @@ class Window
|
||||
void InvalidateArrange(Visual *source);
|
||||
void Relayout();
|
||||
void FocusVisual(Visual *visual);
|
||||
void SetWindowStyle(DWORD style, DWORD extendedStyle = 0);
|
||||
void SetDragRegionHeight(float height);
|
||||
void SetRoundedCorners(bool enabled);
|
||||
|
||||
void SetScene(std::unique_ptr<Scene> scene);
|
||||
|
||||
@@ -56,5 +59,9 @@ class Window
|
||||
Visual *focusedVisual_ = nullptr;
|
||||
Visual *hoveredVisual_ = nullptr;
|
||||
Visual *capturedVisual_ = nullptr;
|
||||
DWORD windowStyle_ = WS_OVERLAPPEDWINDOW;
|
||||
DWORD extendedWindowStyle_ = 0;
|
||||
float dragRegionHeight_ = 0.0f;
|
||||
bool roundedCorners_ = false;
|
||||
};
|
||||
} // namespace msimeui
|
||||
@@ -0,0 +1,32 @@
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[ValidateSet("Debug", "Release")]
|
||||
[string]$Configuration = "Debug",
|
||||
[switch]$Fresh
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||
$repoRoot = Split-Path -Parent $scriptDir
|
||||
$configurePreset = if ($Configuration -eq "Release") { "vcpkg-release" } else { "vcpkg-debug" }
|
||||
$buildPreset = if ($Configuration -eq "Release") { "release" } else { "debug" }
|
||||
|
||||
Push-Location $repoRoot
|
||||
try {
|
||||
$configureArguments = @("--preset", $configurePreset)
|
||||
if ($Fresh) {
|
||||
$configureArguments += "--fresh"
|
||||
}
|
||||
& cmake @configureArguments
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "CMake configure failed with exit code $LASTEXITCODE."
|
||||
}
|
||||
|
||||
& cmake --build --preset $buildPreset --target msimeui-emoji-panel
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "Emoji panel build failed with exit code $LASTEXITCODE."
|
||||
}
|
||||
}
|
||||
finally {
|
||||
Pop-Location
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[ValidateSet("Debug", "Release")]
|
||||
[string]$Configuration = "Debug",
|
||||
[switch]$Build
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||
$repoRoot = Split-Path -Parent $scriptDir
|
||||
|
||||
if ($Build) {
|
||||
& (Join-Path $scriptDir "build-emoji-panel.ps1") -Configuration $Configuration
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
exit $LASTEXITCODE
|
||||
}
|
||||
}
|
||||
|
||||
$panelPath = Join-Path $repoRoot "build/bin/$Configuration/msimeui-emoji-panel.exe"
|
||||
if (-not (Test-Path -LiteralPath $panelPath)) {
|
||||
throw "Emoji panel executable not found: $panelPath. Run build-emoji-panel.ps1 first or pass -Build."
|
||||
}
|
||||
|
||||
Start-Process -FilePath $panelPath -WorkingDirectory $repoRoot
|
||||
+47
-2
@@ -6,6 +6,7 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <d2d1.h>
|
||||
#include <dwmapi.h>
|
||||
#include <sstream>
|
||||
#include <windowsx.h>
|
||||
|
||||
@@ -64,10 +65,15 @@ bool Window::Create()
|
||||
RegisterClassExW(&windowClass);
|
||||
|
||||
const POINT origin = GetCenteredWindowOrigin(width_, height_);
|
||||
hwnd_ = CreateWindowExW(0, className_.c_str(), title_.c_str(), WS_OVERLAPPEDWINDOW, origin.x, origin.y, width_, height_,
|
||||
nullptr, nullptr, instance_, this);
|
||||
hwnd_ = CreateWindowExW(extendedWindowStyle_, className_.c_str(), title_.c_str(), windowStyle_, origin.x, origin.y,
|
||||
width_, height_, nullptr, nullptr, instance_, this);
|
||||
if (hwnd_)
|
||||
{
|
||||
if (roundedCorners_)
|
||||
{
|
||||
const DWM_WINDOW_CORNER_PREFERENCE preference = DWMWCP_ROUND;
|
||||
DwmSetWindowAttribute(hwnd_, DWMWA_WINDOW_CORNER_PREFERENCE, &preference, sizeof(preference));
|
||||
}
|
||||
if (largeIcon)
|
||||
{
|
||||
SendMessageW(hwnd_, WM_SETICON, ICON_BIG, reinterpret_cast<LPARAM>(largeIcon));
|
||||
@@ -412,6 +418,26 @@ LRESULT Window::HandleMessage(UINT message, WPARAM wParam, LPARAM lParam)
|
||||
}
|
||||
break;
|
||||
|
||||
case WM_NCHITTEST:
|
||||
{
|
||||
const LRESULT defaultHit = DefWindowProcW(hwnd_, message, wParam, lParam);
|
||||
if (defaultHit == HTCLIENT && dragRegionHeight_ > 0.0f)
|
||||
{
|
||||
POINT point = {GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)};
|
||||
ScreenToClient(hwnd_, &point);
|
||||
RECT client = {};
|
||||
GetClientRect(hwnd_, &client);
|
||||
const float dragHeightPixels = DipsToPixels(dragRegionHeight_, GetDpi());
|
||||
const float reservedRightPixels = DipsToPixels(58.0f, GetDpi());
|
||||
if (static_cast<float>(point.y) < dragHeightPixels &&
|
||||
static_cast<float>(point.x) < static_cast<float>(client.right) - reservedRightPixels)
|
||||
{
|
||||
return HTCAPTION;
|
||||
}
|
||||
}
|
||||
return defaultHit;
|
||||
}
|
||||
|
||||
case WM_SIZE:
|
||||
OnSize();
|
||||
return 0;
|
||||
@@ -507,6 +533,25 @@ void Window::FocusVisual(Visual *visual)
|
||||
SetFocusedVisual(visual);
|
||||
}
|
||||
|
||||
void Window::SetWindowStyle(DWORD style, DWORD extendedStyle)
|
||||
{
|
||||
if (!hwnd_)
|
||||
{
|
||||
windowStyle_ = style;
|
||||
extendedWindowStyle_ = extendedStyle;
|
||||
}
|
||||
}
|
||||
|
||||
void Window::SetDragRegionHeight(float height)
|
||||
{
|
||||
dragRegionHeight_ = std::max(height, 0.0f);
|
||||
}
|
||||
|
||||
void Window::SetRoundedCorners(bool enabled)
|
||||
{
|
||||
roundedCorners_ = enabled;
|
||||
}
|
||||
|
||||
void Window::SetFocusedVisual(Visual *visual)
|
||||
{
|
||||
if (focusedVisual_ == visual)
|
||||
|
||||
Reference in new issue
Block a user