Skip to content

Commit

Permalink
set_layout and set_candidates
Browse files Browse the repository at this point in the history
  • Loading branch information
eagleoflqj committed Jan 15, 2024
1 parent 6cf95a9 commit ff9e70c
Show file tree
Hide file tree
Showing 15 changed files with 352 additions and 0 deletions.
1 change: 1 addition & 0 deletions .clang-format
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
IndentWidth: 4
48 changes: 48 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
name: ci

on:
push:
branches:
- master
pull_request:

jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Install dependencies
run: sudo apt install -y clang-format

- name: Lint
run: |
find include preview src -name '*.mm' -o -name '*.hpp' | xargs clang-format -Werror --dry-run
build:
needs: lint
runs-on: macos-13
strategy:
fail-fast: false
matrix:
arch: [x86_64, arm64]

steps:
- uses: actions/checkout@v4
with:
submodules: true

- name: Install dependencies
run: |
./install-deps.sh ${{ matrix.arch }}
- name: Build
run: |
PKG_CONFIG_PATH=/tmp/fcitx5/lib/pkgconfig cmake -B build -G Ninja \
-DCMAKE_OSX_ARCHITECTURES=${{ matrix.arch }} \
-DCMAKE_BUILD_TYPE=Release
cmake --build build
- name: Setup tmate session
if: ${{ failure() }}
uses: mxschmitt/action-tmate@v3
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.DS_Store
build
include/html_template.hpp
32 changes: 32 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
cmake_minimum_required(VERSION 3.27)

project(fcitx5-webview)

set(CMAKE_CXX_STANDARD 17)

option(BUILD_PREVIEW "Build preview app for development" ON)

if(NOT CMAKE_OSX_ARCHITECTURES)
set(CMAKE_OSX_ARCHITECTURES "${CMAKE_HOST_SYSTEM_PROCESSOR}")
endif()

find_package(PkgConfig REQUIRED)
pkg_check_modules(JsonC REQUIRED IMPORTED_TARGET "json-c")

include_directories(webview)
add_custom_target(patch_webview
COMMAND git reset --hard
COMMAND git apply "${PROJECT_SOURCE_DIR}/patches/webview.patch"
WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}/webview"
)

file(READ "${PROJECT_SOURCE_DIR}/index.html" HTML_TEMPLATE)
configure_file("${PROJECT_SOURCE_DIR}/include/html_template.hpp.in"
"${PROJECT_SOURCE_DIR}/include/html_template.hpp"
)

add_subdirectory(src)

if(BUILD_PREVIEW)
add_subdirectory(preview)
endif()
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,16 @@

Customizable candidate window for [fcitx5-macos](https://github.com/fcitx-contrib/fcitx5-macos),
powered by [webview](https://github.com/webview/webview).

It can be developed independently of fcitx5.

## Build
```sh
./install-deps.sh
PKG_CONFIG_PATH=/tmp/fcitx5/lib/pkgconfig cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Debug
cmake --build build
```
## Preview
```sh
build/preview/preview.app/Contents/MacOS/preview
```
38 changes: 38 additions & 0 deletions include/candidate_window.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#ifndef CANDIDATE_WINDOW_H
#define CANDIDATE_WINDOW_H

#include <functional>
#include <string>
#include <vector>

namespace candidate_window {
enum layout_t {
horizontal,
vertical,
};

enum format_t {
Underline = (1 << 3),
HighLight = (1 << 4),
Bold = (1 << 6),
Strike = (1 << 7),
Italic = (1 << 8),
};

class CandidateWindow {
public:
virtual ~CandidateWindow() = default;
virtual void set_layout(layout_t layout) = 0;
virtual void set_preedit_mode(bool enabled) = 0;
virtual void set_preedit(const std::vector<std::string> &text,
const std::vector<format_t> format) = 0;
virtual void set_labels(const std::vector<std::string> &labels) = 0;
virtual void set_candidates(const std::vector<std::string> &candidates) = 0;
virtual void set_highlight_callback(std::function<void(size_t index)>) = 0;
virtual void set_select_callback(std::function<void(size_t index)>) = 0;
virtual void set_style(const void *style) = 0;
virtual void show() = 0;
virtual void hide() = 0;
};
} // namespace candidate_window
#endif
1 change: 1 addition & 0 deletions include/html_template.hpp.in
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
#define HTML_TEMPLATE R"DELIMITER(@HTML_TEMPLATE@)DELIMITER"
30 changes: 30 additions & 0 deletions include/webview_candidate_window.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#ifndef WEBVIEW_CANDIDATE_WINDOW_H
#define WEBVIEW_CANDIDATE_WINDOW_H

#include "candidate_window.hpp"
#include "webview.h"

namespace candidate_window {
class WebviewCandidateWindow : public CandidateWindow {
public:
WebviewCandidateWindow();
~WebviewCandidateWindow();
void set_layout(layout_t layout) override;
void set_preedit_mode(bool enabled) override {}
void set_preedit(const std::vector<std::string> &text,
const std::vector<format_t> format) override {}
void set_labels(const std::vector<std::string> &labels) override {}
void set_candidates(const std::vector<std::string> &candidates) override;
void set_highlight_callback(std::function<void(size_t index)>) override {}
void set_select_callback(std::function<void(size_t index)>) override {}
void set_style(const void *style) override{};
void show() override;
void hide() override;

private:
void set_transparent_background();
webview::webview w_;
std::thread thread_;
};
} // namespace candidate_window
#endif
58 changes: 58 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<!DOCTYPE html>
<html>
<head>
<style>
body {
background: rgba(0, 0, 0, 0);
}
.candidates {
display: flex;
}
.candidates.vertical {
flex-direction: column;
}
.candidates.horizontal {
flex-direction: row;
}
.candidate {
padding: 8px;
}
</style>
</head>
<body>
<div class="panel">
<div class="preedit"></div>
<div class="candidates">
</div>
</div>
<script>
const candidates = document.querySelector(".candidates")

function setLayout(layout) {
switch (layout) {
case 0:
candidates.classList.remove("vertical")
candidates.classList.add("horizontal")
break
case 1:
candidates.classList.remove("horizontal")
candidates.classList.add("vertical")
}
}

function setCandidates(cands) {
candidates.innerHTML = ""
cands = JSON.parse(cands)
for (let i = 0; i < cands.length; ++i) {
const candidate = document.createElement("div")
candidate.classList.add("candidate")
const text = document.createElement("div")
text.classList.add("text")
text.innerHTML = cands[i]
candidate.appendChild(text)
candidates.appendChild(candidate)
}
}
</script>
</body>
</html>
21 changes: 21 additions & 0 deletions install-deps.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
set -e

if [[ -z $1 ]]; then
ARCH=`uname -m`
else
ARCH=$1
fi

# This is the same with INSTALL_PREFIX of prebuilder
INSTALL_PREFIX=/tmp/fcitx5
mkdir -p $INSTALL_PREFIX

deps=(
json-c
)

for dep in "${deps[@]}"; do
file=$dep-$ARCH.tar.bz2
[[ -f cache/$file ]] || wget -P cache https://github.com/fcitx-contrib/fcitx5-macos-prebuilder/releases/download/latest/$file
tar xjvf cache/$file -C $INSTALL_PREFIX
done
12 changes: 12 additions & 0 deletions patches/webview.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
diff --git a/webview.h b/webview.h
index b256118..7d4b0d7 100644
--- a/webview.h
+++ b/webview.h
@@ -1091,6 +1091,7 @@ public:
}
virtual ~cocoa_wkwebview_engine() = default;
void *window() { return (void *)m_window; }
+ void *view() { return (void *)m_webview; }
void terminate() { stop_run_loop(); }
void run() {
auto app = get_shared_application();
9 changes: 9 additions & 0 deletions preview/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
add_executable(preview
MACOSX_BUNDLE
preview.mm
)

target_include_directories(preview PRIVATE "${PROJECT_SOURCE_DIR}/include")
target_link_libraries(preview WebviewCandidateWindow "-framework WebKit" "-framework Cocoa")

add_dependencies(preview patch_webview)
22 changes: 22 additions & 0 deletions preview/preview.mm
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#include "webview_candidate_window.hpp"
#import <Cocoa/Cocoa.h>

#include <chrono>
#include <thread>

int main(int argc, const char *argv[]) {
@autoreleasepool {
NSApplication *application = [NSApplication sharedApplication];

std::unique_ptr<candidate_window::CandidateWindow> candidateWindow =
std::make_unique<candidate_window::WebviewCandidateWindow>();
auto t = std::thread([&] {
std::this_thread::sleep_for(std::chrono::seconds(1));
candidateWindow->set_layout(candidate_window::layout_t::vertical);
candidateWindow->set_candidates({"虚假的", "候选词"});
candidateWindow->show();
});
[application run];
}
return 0;
}
6 changes: 6 additions & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
add_library(WebviewCandidateWindow webview_candidate_window.mm)

target_include_directories(WebviewCandidateWindow PRIVATE "${PROJECT_SOURCE_DIR}/include")
target_link_libraries(WebviewCandidateWindow "-framework WebKit" PkgConfig::JsonC)

add_dependencies(WebviewCandidateWindow patch_webview)
58 changes: 58 additions & 0 deletions src/webview_candidate_window.mm
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
#include "webview_candidate_window.hpp"
#include "html_template.hpp"
#import <WebKit/WKWebView.h>
#include <json-c/json.h>
#include <sstream>

namespace candidate_window {

WebviewCandidateWindow::WebviewCandidateWindow() {
NSRect frame = NSMakeRect(0, 0, 400, 300);
NSWindow *window =
[[NSWindow alloc] initWithContentRect:frame
styleMask:NSWindowStyleMaskBorderless
backing:NSBackingStoreBuffered
defer:NO];
w_ = webview::webview(1, window);
set_transparent_background();
w_.set_html(HTML_TEMPLATE);
thread_ = std::thread([this] { w_.run(); });
}

WebviewCandidateWindow::~WebviewCandidateWindow() {}

void WebviewCandidateWindow::set_transparent_background() {
// Transparent NSWindow
[static_cast<NSWindow *>(w_.window())
setBackgroundColor:[NSColor colorWithRed:0 green:0 blue:0 alpha:0]];
// Transparent WKWebView
WKWebView *webView = static_cast<WKWebView *>(w_.view());
[webView setValue:@NO forKey:@"drawsBackground"];
[webView setUnderPageBackgroundColor:[NSColor clearColor]];
}

void WebviewCandidateWindow::set_layout(layout_t layout) {
std::stringstream ss;
ss << "setLayout(" << layout << ")";
w_.eval(ss.str());
}

void WebviewCandidateWindow::set_candidates(
const std::vector<std::string> &candidates) {
json_object *array = json_object_new_array();
for (const auto &candidate : candidates) {
json_object_array_add(array, json_object_new_string(candidate.c_str()));
}
std::string json = json_object_to_json_string(array);
w_.eval("setCandidates('" + json + "')");
json_object_put(array);
}

void WebviewCandidateWindow::show() {
[static_cast<NSWindow *>(w_.window()) makeKeyAndOrderFront:nil];
}
void WebviewCandidateWindow::hide() {
[static_cast<NSWindow *>(w_.window()) orderBack:nil];
}

} // namespace candidate_window

0 comments on commit ff9e70c

Please sign in to comment.