6 Commits

Author SHA1 Message Date
2c909d35ea feat(chat): message-list rework, customization surface, smooth scroll
Chat tab overhaul:
- Header/layout: single-row header (name + key-lock + compact click-to-copy address +
  right-aligned icon toolbar with a settings "notch"), composer moved below the message
  box (emoji toggle left of a bottom-flush input), tightened list-pane controls.
- Message list: per-day date separators (Today/Yesterday/date), time-only group headers,
  tight same-sender grouping with iMessage-style merged corners, per-run peer avatar,
  delivery status (clock -> check), hover-reveal per-message time. Message base ~18px.
- Customization: a settings "notch" gear opens a modal (also under Settings ->
  Chat & Contacts) with segmented controls for emoji style / bubble style / density /
  timestamps, sliders for poll rate + text size, a bubble-accent dropdown, Enter-to-send.
  Bubble style/accent/density/text-size/timestamps all applied live in the message loop.
- Settings: app-wide clock-format control lives in Settings -> General; chat keeps a
  per-tab override. Both chat panes now use the app's smooth wheel-scroll.
- i18n: new strings across all 8 languages; CJK subset rebuilt for the added glyphs.

Inline contact rename, hide/mute, 0-conf fast-scan and the export path are carried through.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 18:45:19 -05:00
3b5db02e09 feat(app): font-atlas rebuild hook + clock/emoji sync
- requestFontRebuild() + preFrame handling so toggling color emoji swaps the atlas live;
  request a rebuild at startup when the saved setting wants color.
- preFrame syncs Typography's color-emoji flag and the util clock flag from settings.
- Chat 0-conf fast-scan cadence now reads the user's poll-rate setting (was a hardcoded 2.5s).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 18:44:55 -05:00
8d8cd337cf feat(util): app-wide 24h/12h clock format
Add util::formatClockDateTime/formatClockTime driven by a process-wide flag (setClock12h,
synced from the time_format setting each frame). Switch the primary user-facing timestamp
displays to it — the transaction list, wallet-state tx + banned-peer times, the explorer
block time, and the block-info dialog — so one preference drives every clock.

Log files, export filenames/content, console line prefixes, the market chart axis, and the
worker-thread "last updated" string intentionally stay fixed 24h.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 18:44:44 -05:00
acde8c0833 feat(config): chat-customization + app-wide clock settings
Persisted (additive JSON, clamped on load) preferences backing the new chat settings
surface and the global clock:

- chat: emoji style (color default), poll rate, bubble style + accent, message density,
  text scale, per-tab timestamp override, Enter-to-send.
- app-wide time_format (0=24h, 1=12h) that the Chat tab falls back to and every other
  user-facing timestamp now follows.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 18:44:29 -05:00
a78b44246e build(freetype): color-emoji rendering via a cross-platform FreeType backend
The chat "Emoji style: Color" option renders the merged emoji in color (COLR/CPAL
Twemoji) instead of the monochrome NotoEmoji subset. This needs FreeType, which the
default stb_truetype rasterizer can't do for color glyphs.

- Vendor imgui_freetype (matches the bundled 1.92 ImFontLoader API) and embed a 1.4 MB
  COLRv0 Twemoji font (no libpng/harfbuzz needed).
- CMake gains an optional FreeType path: native Linux/macOS use the system FreeType via
  find_package; the mingw-w64 cross-compile has none, so build.sh --win-release now
  cross-builds a minimal static FreeType (scripts/build-freetype-mingw.sh) and passes it
  in. Absent FreeType => graceful monochrome fallback, so no build breaks.
- Typography selects the FreeType loader + the color font (LoadColor) when color emoji is
  on, else the stb loader + mono subset; toggling reloads the atlas. Both the DX11 and
  OpenGL backends already support the 1.92 RGBA dynamic atlas, so color glyphs render.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 18:44:19 -05:00
63ed31d2ec feat(chat): polish thread header, emoji picker, and pane gutter
Tier A + B chat-tab visual pass:
- Widen the list<->thread gutter (1px -> 10px) for breathing room.
- Redesign the thread header: avatar + name on the left with a
  right-aligned frameless icon toolbar (add-contact/export/mute/hide,
  each with a tooltip); the name is clipped to the toolbar's left edge
  so a long contact label can't overrun the icons.
- Replace the "Copy Full Address" button with a click-to-copy shortened
  address (copies the full z-addr), preceded by a key-verify lock whose
  tooltip shows a comparable identity-key fingerprint.
- Show a "Waiting for reply" chip in the header when the peer's identity
  key isn't known yet.
- Emoji picker: frameless grid with tight cells (glyphs fill the cell)
  and leftover width spread into the column gaps so it stays edge-to-edge.
- Larger 30px composer emoji toggle that lights up while the picker is
  open; footer height + byte-counter centering adjusted to match.

New i18n keys (chat_copy_address_tip / chat_verify_key / chat_awaiting_key)
added additively to all 8 languages; CJK subset font rebuilt for the new
zh glyph.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 09:14:44 -05:00
34 changed files with 2358 additions and 232 deletions

4
.gitignore vendored
View File

@@ -54,3 +54,7 @@ third_party/silentdragonxlite/lib/vendor/
# Generated by configure_file from res/ObsidianDragon.manifest.in (do not track) # Generated by configure_file from res/ObsidianDragon.manifest.in (do not track)
res/ObsidianDragon.manifest res/ObsidianDragon.manifest
# Cross-built mingw FreeType (color emoji) — regenerated by scripts/build-freetype-mingw.sh
third_party/freetype-mingw/
third_party/.freetype-mingw-build/

View File

@@ -406,6 +406,35 @@ else()
list(APPEND IMGUI_HEADERS ${IMGUI_DIR}/backends/imgui_impl_opengl3.h) list(APPEND IMGUI_HEADERS ${IMGUI_DIR}/backends/imgui_impl_opengl3.h)
endif() endif()
# Optional FreeType font loader — enables color-emoji rendering (COLR/CPAL Twemoji) when the chat
# "color emoji" setting is on; otherwise the wallet falls back to the monochrome emoji subset.
# - Native Linux/macOS: use the system FreeType via find_package.
# - Windows (mingw cross): the toolchain ships no FreeType, so build.sh --win-release cross-builds a
# static one (scripts/build-freetype-mingw.sh) and passes -DDRAGONX_MINGW_FREETYPE_PREFIX here.
# - Other cross builds (osxcross) without FreeType: silently fall back to monochrome.
set(DRAGONX_FREETYPE OFF)
set(DRAGONX_FREETYPE_LIB "")
set(DRAGONX_FREETYPE_INC "")
if(DEFINED DRAGONX_MINGW_FREETYPE_PREFIX AND EXISTS "${DRAGONX_MINGW_FREETYPE_PREFIX}/lib/libfreetype.a")
set(DRAGONX_FREETYPE ON)
set(DRAGONX_FREETYPE_LIB "${DRAGONX_MINGW_FREETYPE_PREFIX}/lib/libfreetype.a")
set(DRAGONX_FREETYPE_INC "${DRAGONX_MINGW_FREETYPE_PREFIX}/include/freetype2")
message(STATUS "FreeType (mingw cross-built) found — chat color emoji enabled")
elseif(NOT CMAKE_CROSSCOMPILING)
find_package(Freetype QUIET)
if(FREETYPE_FOUND)
set(DRAGONX_FREETYPE ON)
set(DRAGONX_FREETYPE_LIB Freetype::Freetype) # imported target carries include dirs
message(STATUS "FreeType ${FREETYPE_VERSION_STRING} found — chat color emoji enabled")
endif()
endif()
if(DRAGONX_FREETYPE)
list(APPEND IMGUI_SOURCES ${IMGUI_DIR}/misc/freetype/imgui_freetype.cpp)
list(APPEND IMGUI_HEADERS ${IMGUI_DIR}/misc/freetype/imgui_freetype.h)
else()
message(STATUS "FreeType not found — chat color emoji falls back to monochrome")
endif()
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# QR Code library (bundled) # QR Code library (bundled)
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
@@ -730,7 +759,9 @@ ${CMAKE_SOURCE_DIR}/res/fonts/Ubuntu-Medium.ttf;\
${CMAKE_SOURCE_DIR}/res/fonts/UbuntuMono-R.ttf;\ ${CMAKE_SOURCE_DIR}/res/fonts/UbuntuMono-R.ttf;\
${CMAKE_SOURCE_DIR}/res/fonts/MaterialIcons-Regular.ttf;\ ${CMAKE_SOURCE_DIR}/res/fonts/MaterialIcons-Regular.ttf;\
${CMAKE_SOURCE_DIR}/res/fonts/MaterialDesignIcons-Pickaxe-Subset.ttf;\ ${CMAKE_SOURCE_DIR}/res/fonts/MaterialDesignIcons-Pickaxe-Subset.ttf;\
${CMAKE_SOURCE_DIR}/res/fonts/NotoSansCJK-Subset.ttf" ${CMAKE_SOURCE_DIR}/res/fonts/NotoSansCJK-Subset.ttf;\
${CMAKE_SOURCE_DIR}/res/fonts/NotoEmoji-Subset.ttf;\
${CMAKE_SOURCE_DIR}/res/fonts/TwemojiMozilla-Color.ttf"
) )
add_executable(ObsidianDragon add_executable(ObsidianDragon
@@ -863,6 +894,15 @@ else()
target_compile_definitions(ObsidianDragon PRIVATE DRAGONX_HAS_GLAD) target_compile_definitions(ObsidianDragon PRIVATE DRAGONX_HAS_GLAD)
endif() endif()
# Color-emoji font loader (FreeType) — linked + flagged only when found (see DRAGONX_FREETYPE above).
if(DRAGONX_FREETYPE)
target_link_libraries(ObsidianDragon PRIVATE ${DRAGONX_FREETYPE_LIB})
if(DRAGONX_FREETYPE_INC)
target_include_directories(ObsidianDragon PRIVATE ${DRAGONX_FREETYPE_INC})
endif()
target_compile_definitions(ObsidianDragon PRIVATE DRAGONX_HAVE_FREETYPE)
endif()
add_executable(HushChatFixtureCheck add_executable(HushChatFixtureCheck
tools/hushchat_fixture_check.cpp tools/hushchat_fixture_check.cpp
src/chat/chat_protocol.cpp src/chat/chat_protocol.cpp

View File

@@ -725,12 +725,27 @@ HDR
"$SCRIPT_DIR/scripts/fetch-libsodium.sh" --win "$SCRIPT_DIR/scripts/fetch-libsodium.sh" --win
fi fi
# ── FreeType for Windows (color-emoji rendering) ───────────────────────
# The mingw toolchain ships no FreeType; cross-build a minimal static one (COLR/CPAL, no external
# deps). Failure is non-fatal — the wallet just falls back to monochrome emoji.
local FT_MINGW_PREFIX="$SCRIPT_DIR/third_party/freetype-mingw"
if [[ ! -f "$FT_MINGW_PREFIX/lib/libfreetype.a" ]]; then
info "Cross-building FreeType for Windows (color emoji) ..."
"$SCRIPT_DIR/scripts/build-freetype-mingw.sh" "$FT_MINGW_PREFIX" \
|| warn "FreeType cross-build failed — Windows build will use monochrome emoji"
fi
local FT_CMAKE_ARG=()
if [[ -f "$FT_MINGW_PREFIX/lib/libfreetype.a" ]]; then
FT_CMAKE_ARG=(-DDRAGONX_MINGW_FREETYPE_PREFIX="$FT_MINGW_PREFIX")
fi
# ── CMake + build ──────────────────────────────────────────────────────── # ── CMake + build ────────────────────────────────────────────────────────
info "Configuring (cross-compile) ..." info "Configuring (cross-compile) ..."
cmake "$SCRIPT_DIR" \ cmake "$SCRIPT_DIR" \
-DCMAKE_TOOLCHAIN_FILE="$bd/mingw-toolchain.cmake" \ -DCMAKE_TOOLCHAIN_FILE="$bd/mingw-toolchain.cmake" \
-DCMAKE_BUILD_TYPE=Release \ -DCMAKE_BUILD_TYPE=Release \
-DDRAGONX_USE_SYSTEM_SDL3=OFF \ -DDRAGONX_USE_SYSTEM_SDL3=OFF \
"${FT_CMAKE_ARG[@]}" \
"${CMAKE_LITE_ARGS[@]}" "${CMAKE_LITE_ARGS[@]}"
info "Building with $JOBS jobs ..." info "Building with $JOBS jobs ..."

View File

@@ -0,0 +1,744 @@
// dear imgui: FreeType font builder (used as a replacement for the stb_truetype builder)
// (code)
// Get the latest version at https://github.com/ocornut/imgui/tree/master/misc/freetype
// Original code by @vuhdo (Aleksei Skriabin) in 2017, with improvements by @mikesart.
// Maintained since 2019 by @ocornut.
// CHANGELOG
// (minor and older changes stripped away, please see git history for details)
// 2025/06/11: refactored for the new ImFontLoader architecture, and ImGuiBackendFlags_RendererHasTextures support.
// 2024/10/17: added plutosvg support for SVG Fonts (seems faster/better than lunasvg). Enable by using '#define IMGUI_ENABLE_FREETYPE_PLUTOSVG'. (#7927)
// 2023/11/13: added support for ImFontConfig::RasterizationDensity field for scaling render density without scaling metrics.
// 2023/08/01: added support for SVG fonts, enable by using '#define IMGUI_ENABLE_FREETYPE_LUNASVG'. (#6591)
// 2023/01/04: fixed a packing issue which in some occurrences would prevent large amount of glyphs from being packed correctly.
// 2021/08/23: fixed crash when FT_Render_Glyph() fails to render a glyph and returns nullptr.
// 2021/03/05: added ImGuiFreeTypeBuilderFlags_Bitmap to load bitmap glyphs.
// 2021/03/02: set 'atlas->TexPixelsUseColors = true' to help some backends with deciding of a preferred texture format.
// 2021/01/28: added support for color-layered glyphs via ImGuiFreeTypeBuilderFlags_LoadColor (require Freetype 2.10+).
// 2021/01/26: simplified integration by using '#define IMGUI_ENABLE_FREETYPE'. renamed ImGuiFreeType::XXX flags to ImGuiFreeTypeBuilderFlags_XXX for consistency with other API. removed ImGuiFreeType::BuildFontAtlas().
// 2020/06/04: fix for rare case where FT_Get_Char_Index() succeed but FT_Load_Glyph() fails.
// 2019/02/09: added RasterizerFlags::Monochrome flag to disable font anti-aliasing (combine with ::MonoHinting for best results!)
// 2019/01/15: added support for imgui allocators + added FreeType only override function SetAllocatorFunctions().
// 2019/01/10: re-factored to match big update in STB builder. fixed texture height waste. fixed redundant glyphs when merging. support for glyph padding.
// 2018/06/08: added support for ImFontConfig::GlyphMinAdvanceX, GlyphMaxAdvanceX.
// 2018/02/04: moved to main imgui repository (away from http://www.github.com/ocornut/imgui_club)
// 2018/01/22: fix for addition of ImFontAtlas::TexUvscale member.
// 2017/10/22: minor inconsequential change to match change in master (removed an unnecessary statement).
// 2017/09/26: fixes for imgui internal changes.
// 2017/08/26: cleanup, optimizations, support for ImFontConfig::RasterizerFlags, ImFontConfig::RasterizerMultiply.
// 2017/08/16: imported from https://github.com/Vuhdo/imgui_freetype into http://www.github.com/ocornut/imgui_club, updated for latest changes in ImFontAtlas, minor tweaks.
// About Gamma Correct Blending:
// - FreeType assumes blending in linear space rather than gamma space.
// - See https://www.freetype.org/freetype2/docs/reference/ft2-base_interface.html#FT_Render_Glyph
// - For correct results you need to be using sRGB and convert to linear space in the pixel shader output.
// - The default dear imgui styles will be impacted by this change (alpha values will need tweaking).
// FIXME: cfg.OversampleH, OversampleV are not supported, but generally not necessary with this rasterizer because Hinting makes everything look better.
#include "imgui.h"
#ifndef IMGUI_DISABLE
#include "imgui_freetype.h"
#include "imgui_internal.h" // ImMin,ImMax,ImFontAtlasBuild*,
#include <stdint.h>
#include <ft2build.h>
#include FT_FREETYPE_H // <freetype/freetype.h>
#include FT_MODULE_H // <freetype/ftmodapi.h>
#include FT_GLYPH_H // <freetype/ftglyph.h>
#include FT_SIZES_H // <freetype/ftsizes.h>
#include FT_SYNTHESIS_H // <freetype/ftsynth.h>
// Handle LunaSVG and PlutoSVG
#if defined(IMGUI_ENABLE_FREETYPE_LUNASVG) && defined(IMGUI_ENABLE_FREETYPE_PLUTOSVG)
#error "Cannot enable both IMGUI_ENABLE_FREETYPE_LUNASVG and IMGUI_ENABLE_FREETYPE_PLUTOSVG"
#endif
#ifdef IMGUI_ENABLE_FREETYPE_LUNASVG
#include FT_OTSVG_H // <freetype/otsvg.h>
#include FT_BBOX_H // <freetype/ftbbox.h>
#include <lunasvg.h>
#endif
#ifdef IMGUI_ENABLE_FREETYPE_PLUTOSVG
#include <plutosvg.h>
#endif
#if defined(IMGUI_ENABLE_FREETYPE_LUNASVG) || defined (IMGUI_ENABLE_FREETYPE_PLUTOSVG)
#if !((FREETYPE_MAJOR >= 2) && (FREETYPE_MINOR >= 12))
#error IMGUI_ENABLE_FREETYPE_PLUTOSVG or IMGUI_ENABLE_FREETYPE_LUNASVG requires FreeType version >= 2.12
#endif
#endif
#ifdef _MSC_VER
#pragma warning (push)
#pragma warning (disable: 4505) // unreferenced local function has been removed (stb stuff)
#pragma warning (disable: 26812) // [Static Analyzer] The enum type 'xxx' is unscoped. Prefer 'enum class' over 'enum' (Enum.3).
#endif
#ifdef __GNUC__
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wpragmas" // warning: unknown option after '#pragma GCC diagnostic' kind
#pragma GCC diagnostic ignored "-Wunused-function" // warning: 'xxxx' defined but not used
#ifndef __clang__
#pragma GCC diagnostic ignored "-Wsubobject-linkage" // warning: 'xxxx' has a field 'xxxx' whose type uses the anonymous namespace
#endif
#endif
//-------------------------------------------------------------------------
// Data
//-------------------------------------------------------------------------
// Default memory allocators
static void* ImGuiFreeTypeDefaultAllocFunc(size_t size, void* user_data) { IM_UNUSED(user_data); return IM_ALLOC(size); }
static void ImGuiFreeTypeDefaultFreeFunc(void* ptr, void* user_data) { IM_UNUSED(user_data); IM_FREE(ptr); }
// Current memory allocators
static void* (*GImGuiFreeTypeAllocFunc)(size_t size, void* user_data) = ImGuiFreeTypeDefaultAllocFunc;
static void (*GImGuiFreeTypeFreeFunc)(void* ptr, void* user_data) = ImGuiFreeTypeDefaultFreeFunc;
static void* GImGuiFreeTypeAllocatorUserData = nullptr;
// Lunasvg support
#ifdef IMGUI_ENABLE_FREETYPE_LUNASVG
static FT_Error ImGuiLunasvgPortInit(FT_Pointer* state);
static void ImGuiLunasvgPortFree(FT_Pointer* state);
static FT_Error ImGuiLunasvgPortRender(FT_GlyphSlot slot, FT_Pointer* _state);
static FT_Error ImGuiLunasvgPortPresetSlot(FT_GlyphSlot slot, FT_Bool cache, FT_Pointer* _state);
#endif
//-------------------------------------------------------------------------
// Code
//-------------------------------------------------------------------------
#define FT_CEIL(X) (((X + 63) & -64) / 64) // From SDL_ttf: Handy routines for converting from fixed point
#define FT_SCALEFACTOR 64.0f
// Glyph metrics:
// --------------
//
// xmin xmax
// | |
// |<-------- width -------->|
// | |
// | +-------------------------+----------------- ymax
// | | ggggggggg ggggg | ^ ^
// | | g:::::::::ggg::::g | | |
// | | g:::::::::::::::::g | | |
// | | g::::::ggggg::::::gg | | |
// | | g:::::g g:::::g | | |
// offsetX -|-------->| g:::::g g:::::g | offsetY |
// | | g:::::g g:::::g | | |
// | | g::::::g g:::::g | | |
// | | g:::::::ggggg:::::g | | |
// | | g::::::::::::::::g | | height
// | | gg::::::::::::::g | | |
// baseline ---*---------|---- gggggggg::::::g-----*-------- |
// / | | g:::::g | |
// origin | | gggggg g:::::g | |
// | | g:::::gg gg:::::g | |
// | | g::::::ggg:::::::g | |
// | | gg:::::::::::::g | |
// | | ggg::::::ggg | |
// | | gggggg | v
// | +-------------------------+----------------- ymin
// | |
// |------------- advanceX ----------->|
// Stored in ImFontAtlas::FontLoaderData. ALLOCATED BY US.
struct ImGui_ImplFreeType_Data
{
FT_Library Library;
FT_MemoryRec_ MemoryManager;
ImGui_ImplFreeType_Data() { memset((void*)this, 0, sizeof(*this)); }
};
// Stored in ImFontConfig::FontLoaderData. ALLOCATED BY US.
struct ImGui_ImplFreeType_FontSrcData
{
// Initialize from an external data buffer. Doesn't copy data, and you must ensure it stays valid up to this object lifetime.
bool InitFont(FT_Library ft_library, const ImFontConfig* src, ImGuiFreeTypeLoaderFlags extra_user_flags);
void CloseFont();
ImGui_ImplFreeType_FontSrcData() { memset((void*)this, 0, sizeof(*this)); }
~ImGui_ImplFreeType_FontSrcData() { CloseFont(); }
// Members
FT_Face FtFace;
ImGuiFreeTypeLoaderFlags UserFlags; // = ImFontConfig::FontLoaderFlags
FT_Int32 LoadFlags;
ImFontBaked* BakedLastActivated;
};
// Stored in ImFontBaked::FontLoaderDatas: pointer to SourcesCount instances of this. ALLOCATED BY CORE.
struct ImGui_ImplFreeType_FontSrcBakedData
{
FT_Size FtSize; // This represent a FT_Face with a given size.
ImGui_ImplFreeType_FontSrcBakedData() { memset((void*)this, 0, sizeof(*this)); }
};
bool ImGui_ImplFreeType_FontSrcData::InitFont(FT_Library ft_library, const ImFontConfig* src, ImGuiFreeTypeLoaderFlags extra_font_loader_flags)
{
FT_Error error = FT_New_Memory_Face(ft_library, (const FT_Byte*)src->FontData, (FT_Long)src->FontDataSize, (FT_Long)src->FontNo, &FtFace);
if (error != 0)
return false;
error = FT_Select_Charmap(FtFace, FT_ENCODING_UNICODE);
if (error != 0)
return false;
// Convert to FreeType flags (NB: Bold and Oblique are processed separately)
UserFlags = (ImGuiFreeTypeLoaderFlags)(src->FontLoaderFlags | extra_font_loader_flags);
LoadFlags = 0;
if ((UserFlags & ImGuiFreeTypeLoaderFlags_Bitmap) == 0)
LoadFlags |= FT_LOAD_NO_BITMAP;
if (UserFlags & ImGuiFreeTypeLoaderFlags_NoHinting)
LoadFlags |= FT_LOAD_NO_HINTING;
if (UserFlags & ImGuiFreeTypeLoaderFlags_NoAutoHint)
LoadFlags |= FT_LOAD_NO_AUTOHINT;
if (UserFlags & ImGuiFreeTypeLoaderFlags_ForceAutoHint)
LoadFlags |= FT_LOAD_FORCE_AUTOHINT;
if (UserFlags & ImGuiFreeTypeLoaderFlags_LightHinting)
LoadFlags |= FT_LOAD_TARGET_LIGHT;
else if (UserFlags & ImGuiFreeTypeLoaderFlags_MonoHinting)
LoadFlags |= FT_LOAD_TARGET_MONO;
else
LoadFlags |= FT_LOAD_TARGET_NORMAL;
if (UserFlags & ImGuiFreeTypeLoaderFlags_LoadColor)
LoadFlags |= FT_LOAD_COLOR;
return true;
}
void ImGui_ImplFreeType_FontSrcData::CloseFont()
{
if (FtFace)
{
FT_Done_Face(FtFace);
FtFace = nullptr;
}
}
static const FT_Glyph_Metrics* ImGui_ImplFreeType_LoadGlyph(ImGui_ImplFreeType_FontSrcData* src_data, uint32_t codepoint)
{
uint32_t glyph_index = FT_Get_Char_Index(src_data->FtFace, codepoint);
if (glyph_index == 0)
return nullptr;
// If this crash for you: FreeType 2.11.0 has a crash bug on some bitmap/colored fonts.
// - https://gitlab.freedesktop.org/freetype/freetype/-/issues/1076
// - https://github.com/ocornut/imgui/issues/4567
// - https://github.com/ocornut/imgui/issues/4566
// You can use FreeType 2.10, or the patched version of 2.11.0 in VcPkg, or probably any upcoming FreeType version.
FT_Error error = FT_Load_Glyph(src_data->FtFace, glyph_index, src_data->LoadFlags);
if (error)
return nullptr;
// Need an outline for this to work
FT_GlyphSlot slot = src_data->FtFace->glyph;
#if defined(IMGUI_ENABLE_FREETYPE_LUNASVG) || defined(IMGUI_ENABLE_FREETYPE_PLUTOSVG)
IM_ASSERT(slot->format == FT_GLYPH_FORMAT_OUTLINE || slot->format == FT_GLYPH_FORMAT_BITMAP || slot->format == FT_GLYPH_FORMAT_SVG);
#else
#if ((FREETYPE_MAJOR >= 2) && (FREETYPE_MINOR >= 12))
IM_ASSERT(slot->format != FT_GLYPH_FORMAT_SVG && "The font contains SVG glyphs, you'll need to enable IMGUI_ENABLE_FREETYPE_PLUTOSVG or IMGUI_ENABLE_FREETYPE_LUNASVG in imconfig.h and install required libraries in order to use this font");
#endif
IM_ASSERT(slot->format == FT_GLYPH_FORMAT_OUTLINE || slot->format == FT_GLYPH_FORMAT_BITMAP);
#endif // IMGUI_ENABLE_FREETYPE_LUNASVG
// Apply convenience transform (this is not picking from real "Bold"/"Italic" fonts! Merely applying FreeType helper transform. Oblique == Slanting)
if (src_data->UserFlags & ImGuiFreeTypeLoaderFlags_Bold)
FT_GlyphSlot_Embolden(slot);
if (src_data->UserFlags & ImGuiFreeTypeLoaderFlags_Oblique)
{
FT_GlyphSlot_Oblique(slot);
//FT_BBox bbox;
//FT_Outline_Get_BBox(&slot->outline, &bbox);
//slot->metrics.width = bbox.xMax - bbox.xMin;
//slot->metrics.height = bbox.yMax - bbox.yMin;
}
return &slot->metrics;
}
static void ImGui_ImplFreeType_BlitGlyph(const FT_Bitmap* ft_bitmap, uint32_t* dst, uint32_t dst_pitch)
{
IM_ASSERT(ft_bitmap != nullptr);
const uint32_t w = ft_bitmap->width;
const uint32_t h = ft_bitmap->rows;
const uint8_t* src = ft_bitmap->buffer;
const uint32_t src_pitch = ft_bitmap->pitch;
switch (ft_bitmap->pixel_mode)
{
case FT_PIXEL_MODE_GRAY: // Grayscale image, 1 byte per pixel.
{
for (uint32_t y = 0; y < h; y++, src += src_pitch, dst += dst_pitch)
for (uint32_t x = 0; x < w; x++)
dst[x] = IM_COL32(255, 255, 255, src[x]);
break;
}
case FT_PIXEL_MODE_MONO: // Monochrome image, 1 bit per pixel. The bits in each byte are ordered from MSB to LSB.
{
for (uint32_t y = 0; y < h; y++, src += src_pitch, dst += dst_pitch)
{
uint8_t bits = 0;
const uint8_t* bits_ptr = src;
for (uint32_t x = 0; x < w; x++, bits <<= 1)
{
if ((x & 7) == 0)
bits = *bits_ptr++;
dst[x] = IM_COL32(255, 255, 255, (bits & 0x80) ? 255 : 0);
}
}
break;
}
case FT_PIXEL_MODE_BGRA:
{
// FIXME: Converting pre-multiplied alpha to straight. Doesn't smell good.
#define DE_MULTIPLY(color, alpha) ImMin((ImU32)(255.0f * (float)color / (float)(alpha + FLT_MIN) + 0.5f), 255u)
for (uint32_t y = 0; y < h; y++, src += src_pitch, dst += dst_pitch)
for (uint32_t x = 0; x < w; x++)
{
uint8_t r = src[x * 4 + 2], g = src[x * 4 + 1], b = src[x * 4], a = src[x * 4 + 3];
dst[x] = IM_COL32(DE_MULTIPLY(r, a), DE_MULTIPLY(g, a), DE_MULTIPLY(b, a), a);
}
#undef DE_MULTIPLY
break;
}
default:
IM_ASSERT(0 && "FreeTypeFont::BlitGlyph(): Unknown bitmap pixel mode!");
}
}
// FreeType memory allocation callbacks
static void* FreeType_Alloc(FT_Memory /*memory*/, long size)
{
return GImGuiFreeTypeAllocFunc((size_t)size, GImGuiFreeTypeAllocatorUserData);
}
static void FreeType_Free(FT_Memory /*memory*/, void* block)
{
GImGuiFreeTypeFreeFunc(block, GImGuiFreeTypeAllocatorUserData);
}
static void* FreeType_Realloc(FT_Memory /*memory*/, long cur_size, long new_size, void* block)
{
// Implement realloc() as we don't ask user to provide it.
if (block == nullptr)
return GImGuiFreeTypeAllocFunc((size_t)new_size, GImGuiFreeTypeAllocatorUserData);
if (new_size == 0)
{
GImGuiFreeTypeFreeFunc(block, GImGuiFreeTypeAllocatorUserData);
return nullptr;
}
if (new_size > cur_size)
{
void* new_block = GImGuiFreeTypeAllocFunc((size_t)new_size, GImGuiFreeTypeAllocatorUserData);
memcpy(new_block, block, (size_t)cur_size);
GImGuiFreeTypeFreeFunc(block, GImGuiFreeTypeAllocatorUserData);
return new_block;
}
return block;
}
static bool ImGui_ImplFreeType_LoaderInit(ImFontAtlas* atlas)
{
IM_ASSERT(atlas->FontLoaderData == nullptr);
ImGui_ImplFreeType_Data* bd = IM_NEW(ImGui_ImplFreeType_Data)();
// FreeType memory management: https://www.freetype.org/freetype2/docs/design/design-4.html
bd->MemoryManager.user = nullptr;
bd->MemoryManager.alloc = &FreeType_Alloc;
bd->MemoryManager.free = &FreeType_Free;
bd->MemoryManager.realloc = &FreeType_Realloc;
// https://www.freetype.org/freetype2/docs/reference/ft2-module_management.html#FT_New_Library
FT_Error error = FT_New_Library(&bd->MemoryManager, &bd->Library);
if (error != 0)
{
IM_DELETE(bd);
return false;
}
// If you don't call FT_Add_Default_Modules() the rest of code may work, but FreeType won't use our custom allocator.
FT_Add_Default_Modules(bd->Library);
#ifdef IMGUI_ENABLE_FREETYPE_LUNASVG
// Install svg hooks for FreeType
// https://freetype.org/freetype2/docs/reference/ft2-properties.html#svg-hooks
// https://freetype.org/freetype2/docs/reference/ft2-svg_fonts.html#svg_fonts
SVG_RendererHooks hooks = { ImGuiLunasvgPortInit, ImGuiLunasvgPortFree, ImGuiLunasvgPortRender, ImGuiLunasvgPortPresetSlot };
FT_Property_Set(bd->Library, "ot-svg", "svg-hooks", &hooks);
#endif // IMGUI_ENABLE_FREETYPE_LUNASVG
#ifdef IMGUI_ENABLE_FREETYPE_PLUTOSVG
// With plutosvg, use provided hooks
FT_Property_Set(bd->Library, "ot-svg", "svg-hooks", plutosvg_ft_svg_hooks());
#endif // IMGUI_ENABLE_FREETYPE_PLUTOSVG
// Store our data
atlas->FontLoaderData = (void*)bd;
return true;
}
static void ImGui_ImplFreeType_LoaderShutdown(ImFontAtlas* atlas)
{
ImGui_ImplFreeType_Data* bd = (ImGui_ImplFreeType_Data*)atlas->FontLoaderData;
IM_ASSERT(bd != nullptr);
FT_Done_Library(bd->Library);
IM_DELETE(bd);
atlas->FontLoaderData = nullptr;
}
static bool ImGui_ImplFreeType_FontSrcInit(ImFontAtlas* atlas, ImFontConfig* src)
{
ImGui_ImplFreeType_Data* bd = (ImGui_ImplFreeType_Data*)atlas->FontLoaderData;
ImGui_ImplFreeType_FontSrcData* bd_font_data = IM_NEW(ImGui_ImplFreeType_FontSrcData);
IM_ASSERT(src->FontLoaderData == nullptr);
src->FontLoaderData = bd_font_data;
if (!bd_font_data->InitFont(bd->Library, src, (ImGuiFreeTypeLoaderFlags)atlas->FontLoaderFlags))
{
IM_DELETE(bd_font_data);
src->FontLoaderData = nullptr;
return false;
}
return true;
}
static void ImGui_ImplFreeType_FontSrcDestroy(ImFontAtlas* atlas, ImFontConfig* src)
{
IM_UNUSED(atlas);
ImGui_ImplFreeType_FontSrcData* bd_font_data = (ImGui_ImplFreeType_FontSrcData*)src->FontLoaderData;
IM_DELETE(bd_font_data);
src->FontLoaderData = nullptr;
}
static bool ImGui_ImplFreeType_FontBakedInit(ImFontAtlas* atlas, ImFontConfig* src, ImFontBaked* baked, void* loader_data_for_baked_src)
{
IM_UNUSED(atlas);
float size = baked->Size;
if (src->MergeMode && src->SizePixels != 0.0f)
size *= (src->SizePixels / baked->OwnerFont->Sources[0]->SizePixels);
size *= src->ExtraSizeScale;
ImGui_ImplFreeType_FontSrcData* bd_font_data = (ImGui_ImplFreeType_FontSrcData*)src->FontLoaderData;
bd_font_data->BakedLastActivated = baked;
// We use one FT_Size per (source + baked) combination.
ImGui_ImplFreeType_FontSrcBakedData* bd_baked_data = (ImGui_ImplFreeType_FontSrcBakedData*)loader_data_for_baked_src;
IM_ASSERT(bd_baked_data != nullptr);
IM_PLACEMENT_NEW(bd_baked_data) ImGui_ImplFreeType_FontSrcBakedData();
FT_New_Size(bd_font_data->FtFace, &bd_baked_data->FtSize);
FT_Activate_Size(bd_baked_data->FtSize);
// Vuhdo 2017: "I'm not sure how to deal with font sizes properly. As far as I understand, currently ImGui assumes that the 'pixel_height'
// is a maximum height of an any given glyph, i.e. it's the sum of font's ascender and descender. Seems strange to me.
// FT_Set_Pixel_Sizes() doesn't seem to get us the same result."
// (FT_Set_Pixel_Sizes() essentially calls FT_Request_Size() with FT_SIZE_REQUEST_TYPE_NOMINAL)
const float rasterizer_density = src->RasterizerDensity * baked->RasterizerDensity;
FT_Size_RequestRec req;
req.type = (bd_font_data->UserFlags & ImGuiFreeTypeLoaderFlags_Bitmap) ? FT_SIZE_REQUEST_TYPE_NOMINAL : FT_SIZE_REQUEST_TYPE_REAL_DIM;
req.width = 0;
req.height = (uint32_t)(size * 64 * rasterizer_density);
req.horiResolution = 0;
req.vertResolution = 0;
FT_Request_Size(bd_font_data->FtFace, &req);
// Output
if (src->MergeMode == false)
{
// Read metrics
FT_Size_Metrics metrics = bd_baked_data->FtSize->metrics;
const float scale = 1.0f / (rasterizer_density * src->ExtraSizeScale);
baked->Ascent = (float)FT_CEIL(metrics.ascender) * scale; // The pixel extents above the baseline in pixels (typically positive).
baked->Descent = (float)FT_CEIL(metrics.descender) * scale; // The extents below the baseline in pixels (typically negative).
//LineSpacing = (float)FT_CEIL(metrics.height) * scale; // The baseline-to-baseline distance. Note that it usually is larger than the sum of the ascender and descender taken as absolute values. There is also no guarantee that no glyphs extend above or below subsequent baselines when using this distance. Think of it as a value the designer of the font finds appropriate.
//LineGap = (float)FT_CEIL(metrics.height - metrics.ascender + metrics.descender) * scale; // The spacing in pixels between one row's descent and the next row's ascent.
//MaxAdvanceWidth = (float)FT_CEIL(metrics.max_advance) * scale; // This field gives the maximum horizontal cursor advance for all glyphs in the font.
}
return true;
}
static void ImGui_ImplFreeType_FontBakedDestroy(ImFontAtlas* atlas, ImFontConfig* src, ImFontBaked* baked, void* loader_data_for_baked_src)
{
IM_UNUSED(atlas);
IM_UNUSED(baked);
IM_UNUSED(src);
ImGui_ImplFreeType_FontSrcBakedData* bd_baked_data = (ImGui_ImplFreeType_FontSrcBakedData*)loader_data_for_baked_src;
IM_ASSERT(bd_baked_data != nullptr);
FT_Done_Size(bd_baked_data->FtSize);
bd_baked_data->~ImGui_ImplFreeType_FontSrcBakedData(); // ~IM_PLACEMENT_DELETE()
}
static bool ImGui_ImplFreeType_FontBakedLoadGlyph(ImFontAtlas* atlas, ImFontConfig* src, ImFontBaked* baked, void* loader_data_for_baked_src, ImWchar codepoint, ImFontGlyph* out_glyph, float* out_advance_x)
{
ImGui_ImplFreeType_FontSrcData* bd_font_data = (ImGui_ImplFreeType_FontSrcData*)src->FontLoaderData;
uint32_t glyph_index = FT_Get_Char_Index(bd_font_data->FtFace, codepoint);
if (glyph_index == 0)
return false;
if (bd_font_data->BakedLastActivated != baked) // <-- could use id
{
// Activate current size
ImGui_ImplFreeType_FontSrcBakedData* bd_baked_data = (ImGui_ImplFreeType_FontSrcBakedData*)loader_data_for_baked_src;
FT_Activate_Size(bd_baked_data->FtSize);
bd_font_data->BakedLastActivated = baked;
}
const FT_Glyph_Metrics* metrics = ImGui_ImplFreeType_LoadGlyph(bd_font_data, codepoint);
if (metrics == nullptr)
return false;
FT_Face face = bd_font_data->FtFace;
FT_GlyphSlot slot = face->glyph;
const float rasterizer_density = src->RasterizerDensity * baked->RasterizerDensity;
// Load metrics only mode
const float advance_x = (slot->advance.x / FT_SCALEFACTOR) / rasterizer_density;
if (out_advance_x != NULL)
{
IM_ASSERT(out_glyph == NULL);
*out_advance_x = advance_x;
return true;
}
// Render glyph into a bitmap (currently held by FreeType)
FT_Render_Mode render_mode = (bd_font_data->UserFlags & ImGuiFreeTypeLoaderFlags_Monochrome) ? FT_RENDER_MODE_MONO : FT_RENDER_MODE_NORMAL;
FT_Error error = FT_Render_Glyph(slot, render_mode);
const FT_Bitmap* ft_bitmap = &slot->bitmap;
if (error != 0 || ft_bitmap == nullptr)
return false;
const int w = (int)ft_bitmap->width;
const int h = (int)ft_bitmap->rows;
const bool is_visible = (w != 0 && h != 0);
// Prepare glyph
out_glyph->Codepoint = codepoint;
out_glyph->AdvanceX = advance_x;
// Pack and retrieve position inside texture atlas
if (is_visible)
{
ImFontAtlasRectId pack_id = ImFontAtlasPackAddRect(atlas, w, h);
if (pack_id == ImFontAtlasRectId_Invalid)
{
// Pathological out of memory case (TexMaxWidth/TexMaxHeight set too small?)
IM_ASSERT(pack_id != ImFontAtlasRectId_Invalid && "Out of texture memory.");
return false;
}
ImTextureRect* r = ImFontAtlasPackGetRect(atlas, pack_id);
// Render pixels to our temporary buffer
atlas->Builder->TempBuffer.resize(w * h * 4);
uint32_t* temp_buffer = (uint32_t*)atlas->Builder->TempBuffer.Data;
ImGui_ImplFreeType_BlitGlyph(ft_bitmap, temp_buffer, w);
const float ref_size = baked->OwnerFont->Sources[0]->SizePixels;
const float offsets_scale = (ref_size != 0.0f) ? (baked->Size / ref_size) : 1.0f;
float font_off_x = ImFloor(src->GlyphOffset.x * offsets_scale + 0.5f); // Snap scaled offset.
float font_off_y = ImFloor(src->GlyphOffset.y * offsets_scale + 0.5f) + baked->Ascent;
float recip_h = 1.0f / rasterizer_density;
float recip_v = 1.0f / rasterizer_density;
// Register glyph
float glyph_off_x = (float)face->glyph->bitmap_left;
float glyph_off_y = (float)-face->glyph->bitmap_top;
out_glyph->X0 = glyph_off_x * recip_h + font_off_x;
out_glyph->Y0 = glyph_off_y * recip_v + font_off_y;
out_glyph->X1 = (glyph_off_x + w) * recip_h + font_off_x;
out_glyph->Y1 = (glyph_off_y + h) * recip_v + font_off_y;
out_glyph->Visible = true;
out_glyph->Colored = (ft_bitmap->pixel_mode == FT_PIXEL_MODE_BGRA);
out_glyph->PackId = pack_id;
ImFontAtlasBakedSetFontGlyphBitmap(atlas, baked, src, out_glyph, r, (const unsigned char*)temp_buffer, ImTextureFormat_RGBA32, w * 4);
}
return true;
}
static bool ImGui_ImplFreetype_FontSrcContainsGlyph(ImFontAtlas* atlas, ImFontConfig* src, ImWchar codepoint)
{
IM_UNUSED(atlas);
ImGui_ImplFreeType_FontSrcData* bd_font_data = (ImGui_ImplFreeType_FontSrcData*)src->FontLoaderData;
int glyph_index = FT_Get_Char_Index(bd_font_data->FtFace, codepoint);
return glyph_index != 0;
}
const ImFontLoader* ImGuiFreeType::GetFontLoader()
{
static ImFontLoader loader;
loader.Name = "FreeType";
loader.LoaderInit = ImGui_ImplFreeType_LoaderInit;
loader.LoaderShutdown = ImGui_ImplFreeType_LoaderShutdown;
loader.FontSrcInit = ImGui_ImplFreeType_FontSrcInit;
loader.FontSrcDestroy = ImGui_ImplFreeType_FontSrcDestroy;
loader.FontSrcContainsGlyph = ImGui_ImplFreetype_FontSrcContainsGlyph;
loader.FontBakedInit = ImGui_ImplFreeType_FontBakedInit;
loader.FontBakedDestroy = ImGui_ImplFreeType_FontBakedDestroy;
loader.FontBakedLoadGlyph = ImGui_ImplFreeType_FontBakedLoadGlyph;
loader.FontBakedSrcLoaderDataSize = sizeof(ImGui_ImplFreeType_FontSrcBakedData);
return &loader;
}
void ImGuiFreeType::SetAllocatorFunctions(void* (*alloc_func)(size_t sz, void* user_data), void (*free_func)(void* ptr, void* user_data), void* user_data)
{
GImGuiFreeTypeAllocFunc = alloc_func;
GImGuiFreeTypeFreeFunc = free_func;
GImGuiFreeTypeAllocatorUserData = user_data;
}
bool ImGuiFreeType::DebugEditFontLoaderFlags(unsigned int* p_font_loader_flags)
{
bool edited = false;
edited |= ImGui::CheckboxFlags("NoHinting", p_font_loader_flags, ImGuiFreeTypeLoaderFlags_NoHinting);
edited |= ImGui::CheckboxFlags("NoAutoHint", p_font_loader_flags, ImGuiFreeTypeLoaderFlags_NoAutoHint);
edited |= ImGui::CheckboxFlags("ForceAutoHint",p_font_loader_flags, ImGuiFreeTypeLoaderFlags_ForceAutoHint);
edited |= ImGui::CheckboxFlags("LightHinting", p_font_loader_flags, ImGuiFreeTypeLoaderFlags_LightHinting);
edited |= ImGui::CheckboxFlags("MonoHinting", p_font_loader_flags, ImGuiFreeTypeLoaderFlags_MonoHinting);
edited |= ImGui::CheckboxFlags("Bold", p_font_loader_flags, ImGuiFreeTypeLoaderFlags_Bold);
edited |= ImGui::CheckboxFlags("Oblique", p_font_loader_flags, ImGuiFreeTypeLoaderFlags_Oblique);
edited |= ImGui::CheckboxFlags("Monochrome", p_font_loader_flags, ImGuiFreeTypeLoaderFlags_Monochrome);
edited |= ImGui::CheckboxFlags("LoadColor", p_font_loader_flags, ImGuiFreeTypeLoaderFlags_LoadColor);
edited |= ImGui::CheckboxFlags("Bitmap", p_font_loader_flags, ImGuiFreeTypeLoaderFlags_Bitmap);
return edited;
}
#ifdef IMGUI_ENABLE_FREETYPE_LUNASVG
// For more details, see https://gitlab.freedesktop.org/freetype/freetype-demos/-/blob/master/src/rsvg-port.c
// The original code from the demo is licensed under CeCILL-C Free Software License Agreement (https://gitlab.freedesktop.org/freetype/freetype/-/blob/master/LICENSE.TXT)
struct LunasvgPortState
{
FT_Error err = FT_Err_Ok;
lunasvg::Matrix matrix;
std::unique_ptr<lunasvg::Document> svg = nullptr;
};
static FT_Error ImGuiLunasvgPortInit(FT_Pointer* _state)
{
*_state = IM_NEW(LunasvgPortState)();
return FT_Err_Ok;
}
static void ImGuiLunasvgPortFree(FT_Pointer* _state)
{
IM_DELETE(*(LunasvgPortState**)_state);
}
static FT_Error ImGuiLunasvgPortRender(FT_GlyphSlot slot, FT_Pointer* _state)
{
LunasvgPortState* state = *(LunasvgPortState**)_state;
// If there was an error while loading the svg in ImGuiLunasvgPortPresetSlot(), the renderer hook still get called, so just returns the error.
if (state->err != FT_Err_Ok)
return state->err;
// rows is height, pitch (or stride) equals to width * sizeof(int32)
lunasvg::Bitmap bitmap((uint8_t*)slot->bitmap.buffer, slot->bitmap.width, slot->bitmap.rows, slot->bitmap.pitch);
#if LUNASVG_VERSION_MAJOR >= 3
state->svg->render(bitmap, state->matrix); // state->matrix is already scaled and translated
#else
state->svg->setMatrix(state->svg->matrix().identity()); // Reset the svg matrix to the default value
state->svg->render(bitmap, state->matrix); // state->matrix is already scaled and translated
#endif
state->err = FT_Err_Ok;
return state->err;
}
static FT_Error ImGuiLunasvgPortPresetSlot(FT_GlyphSlot slot, FT_Bool cache, FT_Pointer* _state)
{
FT_SVG_Document document = (FT_SVG_Document)slot->other;
LunasvgPortState* state = *(LunasvgPortState**)_state;
FT_Size_Metrics& metrics = document->metrics;
// This function is called twice, once in the FT_Load_Glyph() and another right before ImGuiLunasvgPortRender().
// If it's the latter, don't do anything because it's // already done in the former.
if (cache)
return state->err;
state->svg = lunasvg::Document::loadFromData((const char*)document->svg_document, document->svg_document_length);
if (state->svg == nullptr)
{
state->err = FT_Err_Invalid_SVG_Document;
return state->err;
}
#if LUNASVG_VERSION_MAJOR >= 3
lunasvg::Box box = state->svg->boundingBox();
#else
lunasvg::Box box = state->svg->box();
#endif
double scale = std::min(metrics.x_ppem / box.w, metrics.y_ppem / box.h);
double xx = (double)document->transform.xx / (1 << 16);
double xy = -(double)document->transform.xy / (1 << 16);
double yx = -(double)document->transform.yx / (1 << 16);
double yy = (double)document->transform.yy / (1 << 16);
double x0 = (double)document->delta.x / 64 * box.w / metrics.x_ppem;
double y0 = -(double)document->delta.y / 64 * box.h / metrics.y_ppem;
#if LUNASVG_VERSION_MAJOR >= 3
// Scale, transform and pre-translate the matrix for the rendering step
state->matrix = lunasvg::Matrix::translated(-box.x, -box.y);
state->matrix.multiply(lunasvg::Matrix(xx, xy, yx, yy, x0, y0));
state->matrix.scale(scale, scale);
// Apply updated transformation to the bounding box
box.transform(state->matrix);
#else
// Scale and transform, we don't translate the svg yet
state->matrix.identity();
state->matrix.scale(scale, scale);
state->matrix.transform(xx, xy, yx, yy, x0, y0);
state->svg->setMatrix(state->matrix);
// Pre-translate the matrix for the rendering step
state->matrix.translate(-box.x, -box.y);
// Get the box again after the transformation
box = state->svg->box();
#endif
// Calculate the bitmap size
slot->bitmap_left = FT_Int(box.x);
slot->bitmap_top = FT_Int(-box.y);
slot->bitmap.rows = (unsigned int)(ImCeil((float)box.h));
slot->bitmap.width = (unsigned int)(ImCeil((float)box.w));
slot->bitmap.pitch = slot->bitmap.width * 4;
slot->bitmap.pixel_mode = FT_PIXEL_MODE_BGRA;
// Compute all the bearings and set them correctly. The outline is scaled already, we just need to use the bounding box.
double metrics_width = box.w;
double metrics_height = box.h;
double horiBearingX = box.x;
double horiBearingY = -box.y;
double vertBearingX = slot->metrics.horiBearingX / 64.0 - slot->metrics.horiAdvance / 64.0 / 2.0;
double vertBearingY = (slot->metrics.vertAdvance / 64.0 - slot->metrics.height / 64.0) / 2.0;
slot->metrics.width = FT_Pos(IM_ROUND(metrics_width * 64.0)); // Using IM_ROUND() assume width and height are positive
slot->metrics.height = FT_Pos(IM_ROUND(metrics_height * 64.0));
slot->metrics.horiBearingX = FT_Pos(horiBearingX * 64);
slot->metrics.horiBearingY = FT_Pos(horiBearingY * 64);
slot->metrics.vertBearingX = FT_Pos(vertBearingX * 64);
slot->metrics.vertBearingY = FT_Pos(vertBearingY * 64);
if (slot->metrics.vertAdvance == 0)
slot->metrics.vertAdvance = FT_Pos(metrics_height * 1.2 * 64.0);
state->err = FT_Err_Ok;
return state->err;
}
#endif // #ifdef IMGUI_ENABLE_FREETYPE_LUNASVG
//-----------------------------------------------------------------------------
#ifdef __GNUC__
#pragma GCC diagnostic pop
#endif
#ifdef _MSC_VER
#pragma warning (pop)
#endif
#endif // #ifndef IMGUI_DISABLE

View File

@@ -0,0 +1,83 @@
// dear imgui: FreeType font builder (used as a replacement for the stb_truetype builder)
// (headers)
#pragma once
#include "imgui.h" // IMGUI_API
#ifndef IMGUI_DISABLE
// Usage:
// - Add '#define IMGUI_ENABLE_FREETYPE' in your imconfig to automatically enable support
// for imgui_freetype in imgui. It is equivalent to selecting the default loader with:
// io.Fonts->SetFontLoader(ImGuiFreeType::GetFontLoader())
// Optional support for OpenType SVG fonts:
// - Add '#define IMGUI_ENABLE_FREETYPE_PLUTOSVG' to use plutosvg (not provided). See #7927.
// - Add '#define IMGUI_ENABLE_FREETYPE_LUNASVG' to use lunasvg (not provided). See #6591.
// Forward declarations
struct ImFontAtlas;
struct ImFontLoader;
// Hinting greatly impacts visuals (and glyph sizes).
// - By default, hinting is enabled and the font's native hinter is preferred over the auto-hinter.
// - When disabled, FreeType generates blurrier glyphs, more or less matches the stb_truetype.h
// - The Default hinting mode usually looks good, but may distort glyphs in an unusual way.
// - The Light hinting mode generates fuzzier glyphs but better matches Microsoft's rasterizer.
// You can set those flags globally in ImFontAtlas::FontLoaderFlags
// You can set those flags on a per font basis in ImFontConfig::FontLoaderFlags
typedef unsigned int ImGuiFreeTypeLoaderFlags;
enum ImGuiFreeTypeLoaderFlags_
{
ImGuiFreeTypeLoaderFlags_NoHinting = 1 << 0, // Disable hinting. This generally generates 'blurrier' bitmap glyphs when the glyph are rendered in any of the anti-aliased modes.
ImGuiFreeTypeLoaderFlags_NoAutoHint = 1 << 1, // Disable auto-hinter.
ImGuiFreeTypeLoaderFlags_ForceAutoHint = 1 << 2, // Indicates that the auto-hinter is preferred over the font's native hinter.
ImGuiFreeTypeLoaderFlags_LightHinting = 1 << 3, // A lighter hinting algorithm for gray-level modes. Many generated glyphs are fuzzier but better resemble their original shape. This is achieved by snapping glyphs to the pixel grid only vertically (Y-axis), as is done by Microsoft's ClearType and Adobe's proprietary font renderer. This preserves inter-glyph spacing in horizontal text.
ImGuiFreeTypeLoaderFlags_MonoHinting = 1 << 4, // Strong hinting algorithm that should only be used for monochrome output.
ImGuiFreeTypeLoaderFlags_Bold = 1 << 5, // Styling: Should we artificially embolden the font?
ImGuiFreeTypeLoaderFlags_Oblique = 1 << 6, // Styling: Should we slant the font, emulating italic style?
ImGuiFreeTypeLoaderFlags_Monochrome = 1 << 7, // Disable anti-aliasing. Combine this with MonoHinting for best results!
ImGuiFreeTypeLoaderFlags_LoadColor = 1 << 8, // Enable FreeType color-layered glyphs
ImGuiFreeTypeLoaderFlags_Bitmap = 1 << 9, // Enable FreeType bitmap glyphs
#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
ImGuiFreeTypeBuilderFlags_NoHinting = ImGuiFreeTypeLoaderFlags_NoHinting,
ImGuiFreeTypeBuilderFlags_NoAutoHint = ImGuiFreeTypeLoaderFlags_NoAutoHint,
ImGuiFreeTypeBuilderFlags_ForceAutoHint = ImGuiFreeTypeLoaderFlags_ForceAutoHint,
ImGuiFreeTypeBuilderFlags_LightHinting = ImGuiFreeTypeLoaderFlags_LightHinting,
ImGuiFreeTypeBuilderFlags_MonoHinting = ImGuiFreeTypeLoaderFlags_MonoHinting,
ImGuiFreeTypeBuilderFlags_Bold = ImGuiFreeTypeLoaderFlags_Bold,
ImGuiFreeTypeBuilderFlags_Oblique = ImGuiFreeTypeLoaderFlags_Oblique,
ImGuiFreeTypeBuilderFlags_Monochrome = ImGuiFreeTypeLoaderFlags_Monochrome,
ImGuiFreeTypeBuilderFlags_LoadColor = ImGuiFreeTypeLoaderFlags_LoadColor,
ImGuiFreeTypeBuilderFlags_Bitmap = ImGuiFreeTypeLoaderFlags_Bitmap,
#endif
};
// Obsolete names (will be removed)
#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
typedef ImGuiFreeTypeLoaderFlags_ ImGuiFreeTypeBuilderFlags_;
#endif
namespace ImGuiFreeType
{
// This is automatically assigned when using '#define IMGUI_ENABLE_FREETYPE'.
// If you need to dynamically select between multiple builders:
// - you can manually assign this builder with 'atlas->SetFontLoader(ImGuiFreeType::GetFontLoader())'
// - prefer deep-copying this into your own ImFontLoader instance if you use hot-reloading that messes up static data.
IMGUI_API const ImFontLoader* GetFontLoader();
// Override allocators. By default ImGuiFreeType will use IM_ALLOC()/IM_FREE()
// However, as FreeType does lots of allocations we provide a way for the user to redirect it to a separate memory heap if desired.
IMGUI_API void SetAllocatorFunctions(void* (*alloc_func)(size_t sz, void* user_data), void (*free_func)(void* ptr, void* user_data), void* user_data = nullptr);
// Display UI to edit ImFontAtlas::FontLoaderFlags (shared) or ImFontConfig::FontLoaderFlags (single source)
IMGUI_API bool DebugEditFontLoaderFlags(ImGuiFreeTypeLoaderFlags* p_font_loader_flags);
// Obsolete names (will be removed)
#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
//IMGUI_API const ImFontBuilderIO* GetBuilderForFreeType(); // Renamed/changed in 1.92. Change 'io.Fonts->FontBuilderIO = ImGuiFreeType::GetBuilderForFreeType()' to 'io.Fonts->SetFontLoader(ImGuiFreeType::GetFontLoader())' if you need runtime selection.
//static inline bool BuildFontAtlas(ImFontAtlas* atlas, unsigned int flags = 0) { atlas->FontBuilderIO = GetBuilderForFreeType(); atlas->FontLoaderFlags = flags; return atlas->Build(); } // Prefer using '#define IMGUI_ENABLE_FREETYPE'
#endif
}
#endif // #ifndef IMGUI_DISABLE

Binary file not shown.

Binary file not shown.

View File

@@ -135,10 +135,25 @@
"change_pass_title": "Passphrase ändern", "change_pass_title": "Passphrase ändern",
"characters": "Zeichen", "characters": "Zeichen",
"chat": "Chat", "chat": "Chat",
"chat_accent_amber": "Bernstein",
"chat_accent_blue": "Blau",
"chat_accent_green": "Grün",
"chat_accent_pink": "Rosa",
"chat_accent_purple": "Lila",
"chat_accent_theme": "Design",
"chat_add_contact": "Kontakt hinzufügen", "chat_add_contact": "Kontakt hinzufügen",
"chat_awaiting_key": "Warten auf Antwort",
"chat_bubble_minimal": "Minimal",
"chat_bubble_rounded": "Abgerundet",
"chat_bubble_square": "Eckig",
"chat_cancel": "Abbrechen", "chat_cancel": "Abbrechen",
"chat_contact_added": "Kontakt hinzugefügt benenne ihn in Kontakte um", "chat_contact_added": "Kontakt hinzugefügt benenne ihn in Kontakte um",
"chat_contact_request": "kontaktanfrage", "chat_contact_request": "kontaktanfrage",
"chat_copy_address_tip": "Zum Kopieren der Adresse klicken",
"chat_density_comfortable": "Komfortabel",
"chat_density_compact": "Kompakt",
"chat_emoji_color": "Farbig",
"chat_emoji_mono": "Monochrom",
"chat_emoji_search": "Emoji suchen", "chat_emoji_search": "Emoji suchen",
"chat_empty_hint": "Noch keine Unterhaltungen. Nachrichten, die du erhältst, erscheinen hier.", "chat_empty_hint": "Noch keine Unterhaltungen. Nachrichten, die du erhältst, erscheinen hier.",
"chat_empty_start": "Starte eine mit \"Neue Unterhaltung\".", "chat_empty_start": "Starte eine mit \"Neue Unterhaltung\".",
@@ -154,21 +169,39 @@
"chat_len_over": "Nachricht zu lang", "chat_len_over": "Nachricht zu lang",
"chat_locked_hint": "Entsperre deine Wallet, um deine Chats zu laden.", "chat_locked_hint": "Entsperre deine Wallet, um deine Chats zu laden.",
"chat_mute": "Stummschalten", "chat_mute": "Stummschalten",
"chat_new_button": "Neue Unterhaltung", "chat_new_button": "Neuer Chat",
"chat_new_message": "Nachricht", "chat_new_message": "Nachricht",
"chat_new_message_toast": "Neue verschlüsselte Chat-Nachricht", "chat_new_message_toast": "Neue verschlüsselte Chat-Nachricht",
"chat_new_send": "Anfrage senden", "chat_new_send": "Anfrage senden",
"chat_new_title": "Neue Unterhaltung", "chat_new_title": "Neuer Chat",
"chat_new_zaddr": "z-Adresse des Empfängers", "chat_new_zaddr": "z-Adresse des Empfängers",
"chat_no_matches": "Keine Unterhaltungen entsprechen deiner Suche.", "chat_no_matches": "Keine Unterhaltungen entsprechen deiner Suche.",
"chat_no_z_contacts": "Noch keine Kontakte mit geschützter Adresse", "chat_no_z_contacts": "Noch keine Kontakte mit geschützter Adresse",
"chat_opt_bubble_accent": "Blasenfarbe",
"chat_opt_bubble_style": "Blasenstil",
"chat_opt_density": "Nachrichtendichte",
"chat_opt_emoji": "Emoji-Stil",
"chat_opt_enter_sends": "Eingabetaste sendet",
"chat_opt_font_size": "Textgröße",
"chat_opt_global_clock": "Globales Uhrzeitformat",
"chat_opt_poll": "Abrufrate",
"chat_opt_timestamp": "Zeitstempel",
"chat_pick_contact": "Aus Kontakten wählen…", "chat_pick_contact": "Aus Kontakten wählen…",
"chat_rename": "Kontakt umbenennen",
"chat_rename_hint": "Kontaktname",
"chat_renamed": "Kontakt umbenannt",
"chat_retry": "Wiederholen", "chat_retry": "Wiederholen",
"chat_search": "Unterhaltungen durchsuchen", "chat_search": "Unterhaltungen durchsuchen",
"chat_sec_appearance": "DARSTELLUNG",
"chat_sec_messaging": "NACHRICHTEN",
"chat_select_hint": "Wähle eine Unterhaltung aus, um sie anzuzeigen.", "chat_select_hint": "Wähle eine Unterhaltung aus, um sie anzuzeigen.",
"chat_send": "Senden", "chat_send": "Senden",
"chat_send_failed": "nicht gesendet", "chat_send_failed": "nicht gesendet",
"chat_sending": "senden…", "chat_sending": "senden…",
"chat_settings_done": "Fertig",
"chat_settings_section": "CHAT & KONTAKTE",
"chat_settings_tip": "Chat anpassen",
"chat_settings_title": "Chat-Einstellungen",
"chat_show_hidden": "Ausgeblendete anzeigen", "chat_show_hidden": "Ausgeblendete anzeigen",
"chat_time_now": "jetzt", "chat_time_now": "jetzt",
"chat_toast_compose_failed": "Nachricht konnte nicht erstellt werden (zu lang?).", "chat_toast_compose_failed": "Nachricht konnte nicht erstellt werden (zu lang?).",
@@ -179,9 +212,16 @@
"chat_toast_request_compose_failed": "Kontaktanfrage konnte nicht erstellt werden (ungültige Adresse / ungültiger Text?).", "chat_toast_request_compose_failed": "Kontaktanfrage konnte nicht erstellt werden (ungültige Adresse / ungültiger Text?).",
"chat_toast_request_queued": "Kontaktanfrage in Warteschlange.", "chat_toast_request_queued": "Kontaktanfrage in Warteschlange.",
"chat_toast_waiting_reply": "Warte auf die Antwort des Kontakts, bevor du ihm schreiben kannst.", "chat_toast_waiting_reply": "Warte auf die Antwort des Kontakts, bevor du ihm schreiben kannst.",
"chat_today": "Heute",
"chat_ts_12h": "12-Stunden",
"chat_ts_24h": "24-Stunden",
"chat_ts_global": "Global folgen",
"chat_ts_global_short": "Global",
"chat_unhide": "Einblenden", "chat_unhide": "Einblenden",
"chat_unmute": "Stummschaltung aufheben", "chat_unmute": "Stummschaltung aufheben",
"chat_verify_key": "Identitätsschlüssel zum Verifizieren vergleichen",
"chat_waiting_reply": "Warte auf die Antwort dieses Kontakts sobald er antwortet, kannst du ihm schreiben.", "chat_waiting_reply": "Warte auf die Antwort dieses Kontakts sobald er antwortet, kannst du ihm schreiben.",
"chat_yesterday": "Gestern",
"chat_you": "Du", "chat_you": "Du",
"choose_icon": "Symbol wählen", "choose_icon": "Symbol wählen",
"clear": "Leeren", "clear": "Leeren",
@@ -193,6 +233,7 @@
"click_copy_address": "Klicken zum Kopieren der Adresse", "click_copy_address": "Klicken zum Kopieren der Adresse",
"click_copy_uri": "Klicken zum Kopieren der URI", "click_copy_uri": "Klicken zum Kopieren der URI",
"click_to_copy": "Klicken zum Kopieren", "click_to_copy": "Klicken zum Kopieren",
"clock_format": "Uhrzeitformat",
"close": "Schließen", "close": "Schließen",
"conf_count": "%d Best.", "conf_count": "%d Best.",
"confirm_and_send": "Bestätigen & Senden", "confirm_and_send": "Bestätigen & Senden",
@@ -1385,6 +1426,7 @@
"tt_change_pass": "Die Wallet-Verschlüsselungspassphrase ändern", "tt_change_pass": "Die Wallet-Verschlüsselungspassphrase ändern",
"tt_change_pin": "Ihre Entsperr-PIN ändern", "tt_change_pin": "Ihre Entsperr-PIN ändern",
"tt_clear_ztx": "Lokal zwischengespeicherten Z-Transaktionsverlauf löschen", "tt_clear_ztx": "Lokal zwischengespeicherten Z-Transaktionsverlauf löschen",
"tt_clock_format": "24- oder 12-Stunden-Uhr, app-weit. Der Chat-Tab kann sie überschreiben.",
"tt_custom_fees": "Manuelle Gebühreneingabe beim Senden von Transaktionen aktivieren", "tt_custom_fees": "Manuelle Gebühreneingabe beim Senden von Transaktionen aktivieren",
"tt_custom_theme": "Benutzerdefiniertes Theme aktiv", "tt_custom_theme": "Benutzerdefiniertes Theme aktiv",
"tt_daemon_install_bundled": "Node stoppen, den installierten dragonxd mit der in diesem Wallet-Build enthaltenen Version überschreiben und dann neu starten", "tt_daemon_install_bundled": "Node stoppen, den installierten dragonxd mit der in diesem Wallet-Build enthaltenen Version überschreiben und dann neu starten",

View File

@@ -135,10 +135,25 @@
"change_pass_title": "Cambiar frase de contraseña", "change_pass_title": "Cambiar frase de contraseña",
"characters": "caracteres", "characters": "caracteres",
"chat": "Chat", "chat": "Chat",
"chat_accent_amber": "Ámbar",
"chat_accent_blue": "Azul",
"chat_accent_green": "Verde",
"chat_accent_pink": "Rosa",
"chat_accent_purple": "Morado",
"chat_accent_theme": "Tema",
"chat_add_contact": "Añadir contacto", "chat_add_contact": "Añadir contacto",
"chat_awaiting_key": "Esperando respuesta",
"chat_bubble_minimal": "Mínima",
"chat_bubble_rounded": "Redondeada",
"chat_bubble_square": "Cuadrada",
"chat_cancel": "Cancelar", "chat_cancel": "Cancelar",
"chat_contact_added": "Contacto añadido: renómbralo en Contactos", "chat_contact_added": "Contacto añadido: renómbralo en Contactos",
"chat_contact_request": "solicitud de contacto", "chat_contact_request": "solicitud de contacto",
"chat_copy_address_tip": "Clic para copiar la dirección",
"chat_density_comfortable": "Cómoda",
"chat_density_compact": "Compacta",
"chat_emoji_color": "Color",
"chat_emoji_mono": "Monocromo",
"chat_emoji_search": "Buscar emoji", "chat_emoji_search": "Buscar emoji",
"chat_empty_hint": "Aún no hay conversaciones. Los mensajes que recibas aparecerán aquí.", "chat_empty_hint": "Aún no hay conversaciones. Los mensajes que recibas aparecerán aquí.",
"chat_empty_start": "Inicia una con \"Nueva conversación\".", "chat_empty_start": "Inicia una con \"Nueva conversación\".",
@@ -154,21 +169,39 @@
"chat_len_over": "Mensaje demasiado largo", "chat_len_over": "Mensaje demasiado largo",
"chat_locked_hint": "Desbloquea tu monedero para cargar tus chats.", "chat_locked_hint": "Desbloquea tu monedero para cargar tus chats.",
"chat_mute": "Silenciar", "chat_mute": "Silenciar",
"chat_new_button": "Nueva conversación", "chat_new_button": "Nuevo chat",
"chat_new_message": "Mensaje", "chat_new_message": "Mensaje",
"chat_new_message_toast": "Nuevo mensaje de chat cifrado", "chat_new_message_toast": "Nuevo mensaje de chat cifrado",
"chat_new_send": "Enviar solicitud", "chat_new_send": "Enviar solicitud",
"chat_new_title": "Nueva conversación", "chat_new_title": "Nuevo chat",
"chat_new_zaddr": "Dirección z del destinatario", "chat_new_zaddr": "Dirección z del destinatario",
"chat_no_matches": "Ninguna conversación coincide con tu búsqueda.", "chat_no_matches": "Ninguna conversación coincide con tu búsqueda.",
"chat_no_z_contacts": "Aún no hay contactos con dirección blindada", "chat_no_z_contacts": "Aún no hay contactos con dirección blindada",
"chat_opt_bubble_accent": "Color de burbuja",
"chat_opt_bubble_style": "Estilo de burbuja",
"chat_opt_density": "Densidad de mensajes",
"chat_opt_emoji": "Estilo de emoji",
"chat_opt_enter_sends": "Enter envía el mensaje",
"chat_opt_font_size": "Tamaño del texto",
"chat_opt_global_clock": "Formato de reloj global",
"chat_opt_poll": "Frecuencia de sondeo",
"chat_opt_timestamp": "Marcas de tiempo",
"chat_pick_contact": "Elegir de contactos…", "chat_pick_contact": "Elegir de contactos…",
"chat_rename": "Renombrar contacto",
"chat_rename_hint": "Nombre del contacto",
"chat_renamed": "Contacto renombrado",
"chat_retry": "Reintentar", "chat_retry": "Reintentar",
"chat_search": "Buscar conversaciones", "chat_search": "Buscar conversaciones",
"chat_sec_appearance": "APARIENCIA",
"chat_sec_messaging": "MENSAJES",
"chat_select_hint": "Selecciona una conversación para verla.", "chat_select_hint": "Selecciona una conversación para verla.",
"chat_send": "Enviar", "chat_send": "Enviar",
"chat_send_failed": "no enviado", "chat_send_failed": "no enviado",
"chat_sending": "enviando…", "chat_sending": "enviando…",
"chat_settings_done": "Listo",
"chat_settings_section": "CHAT Y CONTACTOS",
"chat_settings_tip": "Personalizar chat",
"chat_settings_title": "Ajustes de chat",
"chat_show_hidden": "Ver ocultos", "chat_show_hidden": "Ver ocultos",
"chat_time_now": "ahora", "chat_time_now": "ahora",
"chat_toast_compose_failed": "No se pudo componer el mensaje (¿demasiado largo?).", "chat_toast_compose_failed": "No se pudo componer el mensaje (¿demasiado largo?).",
@@ -179,9 +212,16 @@
"chat_toast_request_compose_failed": "No se pudo componer la solicitud de contacto (¿dirección o texto no válidos?).", "chat_toast_request_compose_failed": "No se pudo componer la solicitud de contacto (¿dirección o texto no válidos?).",
"chat_toast_request_queued": "Solicitud de contacto en cola.", "chat_toast_request_queued": "Solicitud de contacto en cola.",
"chat_toast_waiting_reply": "Espera a que este contacto responda antes de poder escribirle.", "chat_toast_waiting_reply": "Espera a que este contacto responda antes de poder escribirle.",
"chat_today": "Hoy",
"chat_ts_12h": "12 horas",
"chat_ts_24h": "24 horas",
"chat_ts_global": "Seguir global",
"chat_ts_global_short": "Global",
"chat_unhide": "Mostrar", "chat_unhide": "Mostrar",
"chat_unmute": "Reactivar", "chat_unmute": "Reactivar",
"chat_verify_key": "Clave de identidad: compárala para verificar",
"chat_waiting_reply": "Esperando a que este contacto responda: podrás escribirle una vez lo haga.", "chat_waiting_reply": "Esperando a que este contacto responda: podrás escribirle una vez lo haga.",
"chat_yesterday": "Ayer",
"chat_you": "Tú", "chat_you": "Tú",
"choose_icon": "Elegir Icono", "choose_icon": "Elegir Icono",
"clear": "Limpiar", "clear": "Limpiar",
@@ -193,6 +233,7 @@
"click_copy_address": "Clic para copiar dirección", "click_copy_address": "Clic para copiar dirección",
"click_copy_uri": "Clic para copiar URI", "click_copy_uri": "Clic para copiar URI",
"click_to_copy": "Clic para copiar", "click_to_copy": "Clic para copiar",
"clock_format": "Formato de hora",
"close": "Cerrar", "close": "Cerrar",
"conf_count": "%d conf", "conf_count": "%d conf",
"confirm_and_send": "Confirmar y Enviar", "confirm_and_send": "Confirmar y Enviar",
@@ -1385,6 +1426,7 @@
"tt_change_pass": "Cambiar la contraseña de cifrado de la billetera", "tt_change_pass": "Cambiar la contraseña de cifrado de la billetera",
"tt_change_pin": "Cambiar su PIN de desbloqueo", "tt_change_pin": "Cambiar su PIN de desbloqueo",
"tt_clear_ztx": "Eliminar historial de z-transacciones en caché local", "tt_clear_ztx": "Eliminar historial de z-transacciones en caché local",
"tt_clock_format": "Reloj de 24 o 12 horas, en toda la app. El chat puede anularlo.",
"tt_custom_fees": "Habilitar entrada manual de comisiones al enviar transacciones", "tt_custom_fees": "Habilitar entrada manual de comisiones al enviar transacciones",
"tt_custom_theme": "Tema personalizado activo", "tt_custom_theme": "Tema personalizado activo",
"tt_daemon_install_bundled": "Detiene el nodo, sobrescribe el dragonxd instalado con la versión incluida en esta compilación de la cartera y luego lo reinicia", "tt_daemon_install_bundled": "Detiene el nodo, sobrescribe el dragonxd instalado con la versión incluida en esta compilación de la cartera y luego lo reinicia",

View File

@@ -135,10 +135,25 @@
"change_pass_title": "Changer la phrase secrète", "change_pass_title": "Changer la phrase secrète",
"characters": "caractères", "characters": "caractères",
"chat": "Discussion", "chat": "Discussion",
"chat_accent_amber": "Ambre",
"chat_accent_blue": "Bleu",
"chat_accent_green": "Vert",
"chat_accent_pink": "Rose",
"chat_accent_purple": "Violet",
"chat_accent_theme": "Thème",
"chat_add_contact": "Ajouter un contact", "chat_add_contact": "Ajouter un contact",
"chat_awaiting_key": "En attente de réponse",
"chat_bubble_minimal": "Minimale",
"chat_bubble_rounded": "Arrondie",
"chat_bubble_square": "Carrée",
"chat_cancel": "Annuler", "chat_cancel": "Annuler",
"chat_contact_added": "Contact ajouté — renommez-le dans Contacts", "chat_contact_added": "Contact ajouté — renommez-le dans Contacts",
"chat_contact_request": "demande de contact", "chat_contact_request": "demande de contact",
"chat_copy_address_tip": "Cliquer pour copier l'adresse",
"chat_density_comfortable": "Confortable",
"chat_density_compact": "Compacte",
"chat_emoji_color": "Couleur",
"chat_emoji_mono": "Monochrome",
"chat_emoji_search": "Rechercher un emoji", "chat_emoji_search": "Rechercher un emoji",
"chat_empty_hint": "Aucune conversation pour l'instant. Les messages que vous recevez apparaîtront ici.", "chat_empty_hint": "Aucune conversation pour l'instant. Les messages que vous recevez apparaîtront ici.",
"chat_empty_start": "Commencez-en une avec « Nouvelle conversation ».", "chat_empty_start": "Commencez-en une avec « Nouvelle conversation ».",
@@ -154,21 +169,39 @@
"chat_len_over": "Message trop long", "chat_len_over": "Message trop long",
"chat_locked_hint": "Déverrouillez votre portefeuille pour charger vos discussions.", "chat_locked_hint": "Déverrouillez votre portefeuille pour charger vos discussions.",
"chat_mute": "Muet", "chat_mute": "Muet",
"chat_new_button": "Nouvelle conversation", "chat_new_button": "Nouvelle discussion",
"chat_new_message": "Message", "chat_new_message": "Message",
"chat_new_message_toast": "Nouveau message chiffré", "chat_new_message_toast": "Nouveau message chiffré",
"chat_new_send": "Envoyer la demande", "chat_new_send": "Envoyer la demande",
"chat_new_title": "Nouvelle conversation", "chat_new_title": "Nouvelle discussion",
"chat_new_zaddr": "Adresse Z du destinataire", "chat_new_zaddr": "Adresse Z du destinataire",
"chat_no_matches": "Aucune conversation ne correspond à votre recherche.", "chat_no_matches": "Aucune conversation ne correspond à votre recherche.",
"chat_no_z_contacts": "Aucun contact avec adresse blindée pour l'instant", "chat_no_z_contacts": "Aucun contact avec adresse blindée pour l'instant",
"chat_opt_bubble_accent": "Couleur de bulle",
"chat_opt_bubble_style": "Style de bulle",
"chat_opt_density": "Densité des messages",
"chat_opt_emoji": "Style d'emoji",
"chat_opt_enter_sends": "Entrée envoie le message",
"chat_opt_font_size": "Taille du texte",
"chat_opt_global_clock": "Format d'horloge global",
"chat_opt_poll": "Fréquence d'actualisation",
"chat_opt_timestamp": "Horodatage",
"chat_pick_contact": "Choisir dans les contacts…", "chat_pick_contact": "Choisir dans les contacts…",
"chat_rename": "Renommer le contact",
"chat_rename_hint": "Nom du contact",
"chat_renamed": "Contact renommé",
"chat_retry": "Réessayer", "chat_retry": "Réessayer",
"chat_search": "Rechercher des conversations", "chat_search": "Rechercher des conversations",
"chat_sec_appearance": "APPARENCE",
"chat_sec_messaging": "MESSAGERIE",
"chat_select_hint": "Sélectionnez une conversation pour l'afficher.", "chat_select_hint": "Sélectionnez une conversation pour l'afficher.",
"chat_send": "Envoyer", "chat_send": "Envoyer",
"chat_send_failed": "non envoyé", "chat_send_failed": "non envoyé",
"chat_sending": "envoi…", "chat_sending": "envoi…",
"chat_settings_done": "Terminé",
"chat_settings_section": "CHAT ET CONTACTS",
"chat_settings_tip": "Personnaliser le chat",
"chat_settings_title": "Paramètres du chat",
"chat_show_hidden": "Afficher masqués", "chat_show_hidden": "Afficher masqués",
"chat_time_now": "à l'instant", "chat_time_now": "à l'instant",
"chat_toast_compose_failed": "Impossible de composer le message (trop long ?).", "chat_toast_compose_failed": "Impossible de composer le message (trop long ?).",
@@ -179,9 +212,16 @@
"chat_toast_request_compose_failed": "Impossible de composer la demande de contact (adresse / texte invalide ?).", "chat_toast_request_compose_failed": "Impossible de composer la demande de contact (adresse / texte invalide ?).",
"chat_toast_request_queued": "Demande de contact mise en file d'attente.", "chat_toast_request_queued": "Demande de contact mise en file d'attente.",
"chat_toast_waiting_reply": "En attente de la réponse de ce contact avant de pouvoir lui écrire.", "chat_toast_waiting_reply": "En attente de la réponse de ce contact avant de pouvoir lui écrire.",
"chat_today": "Aujourd'hui",
"chat_ts_12h": "12 heures",
"chat_ts_24h": "24 heures",
"chat_ts_global": "Suivre global",
"chat_ts_global_short": "Global",
"chat_unhide": "Afficher", "chat_unhide": "Afficher",
"chat_unmute": "Réactiver", "chat_unmute": "Réactiver",
"chat_verify_key": "Clé d'identité — comparez pour vérifier",
"chat_waiting_reply": "En attente de la réponse de ce contact — vous pourrez lui écrire dès qu'il aura répondu.", "chat_waiting_reply": "En attente de la réponse de ce contact — vous pourrez lui écrire dès qu'il aura répondu.",
"chat_yesterday": "Hier",
"chat_you": "Vous", "chat_you": "Vous",
"choose_icon": "Choisir une icône", "choose_icon": "Choisir une icône",
"clear": "Effacer", "clear": "Effacer",
@@ -193,6 +233,7 @@
"click_copy_address": "Cliquez pour copier l'adresse", "click_copy_address": "Cliquez pour copier l'adresse",
"click_copy_uri": "Cliquez pour copier l'URI", "click_copy_uri": "Cliquez pour copier l'URI",
"click_to_copy": "Cliquez pour copier", "click_to_copy": "Cliquez pour copier",
"clock_format": "Format d'horloge",
"close": "Fermer", "close": "Fermer",
"conf_count": "%d conf.", "conf_count": "%d conf.",
"confirm_and_send": "Confirmer & Envoyer", "confirm_and_send": "Confirmer & Envoyer",
@@ -1385,6 +1426,7 @@
"tt_change_pass": "Changer la phrase secrète de chiffrement du portefeuille", "tt_change_pass": "Changer la phrase secrète de chiffrement du portefeuille",
"tt_change_pin": "Changer votre PIN de déverrouillage", "tt_change_pin": "Changer votre PIN de déverrouillage",
"tt_clear_ztx": "Supprimer l'historique des z-transactions mis en cache localement", "tt_clear_ztx": "Supprimer l'historique des z-transactions mis en cache localement",
"tt_clock_format": "Horloge 24 h ou 12 h, dans toute l'app. Le chat peut la remplacer.",
"tt_custom_fees": "Activer la saisie manuelle des frais lors de l'envoi de transactions", "tt_custom_fees": "Activer la saisie manuelle des frais lors de l'envoi de transactions",
"tt_custom_theme": "Thème personnalisé actif", "tt_custom_theme": "Thème personnalisé actif",
"tt_daemon_install_bundled": "Arrêter le nœud, remplacer le dragonxd installé par la version intégrée dans cette version du portefeuille, puis redémarrer", "tt_daemon_install_bundled": "Arrêter le nœud, remplacer le dragonxd installé par la version intégrée dans cette version du portefeuille, puis redémarrer",

View File

@@ -135,10 +135,25 @@
"change_pass_title": "パスフレーズを変更", "change_pass_title": "パスフレーズを変更",
"characters": "文字", "characters": "文字",
"chat": "チャット", "chat": "チャット",
"chat_accent_amber": "琥珀",
"chat_accent_blue": "青",
"chat_accent_green": "緑",
"chat_accent_pink": "ピンク",
"chat_accent_purple": "紫",
"chat_accent_theme": "テーマ",
"chat_add_contact": "連絡先に追加", "chat_add_contact": "連絡先に追加",
"chat_awaiting_key": "返信待ち",
"chat_bubble_minimal": "ミニマル",
"chat_bubble_rounded": "角丸",
"chat_bubble_square": "角ばった",
"chat_cancel": "キャンセル", "chat_cancel": "キャンセル",
"chat_contact_added": "連絡先を追加しました — 連絡先で名前を変更できます", "chat_contact_added": "連絡先を追加しました — 連絡先で名前を変更できます",
"chat_contact_request": "連絡リクエスト", "chat_contact_request": "連絡リクエスト",
"chat_copy_address_tip": "クリックしてアドレスをコピー",
"chat_density_comfortable": "ゆったり",
"chat_density_compact": "コンパクト",
"chat_emoji_color": "カラー",
"chat_emoji_mono": "モノクロ",
"chat_emoji_search": "絵文字を検索", "chat_emoji_search": "絵文字を検索",
"chat_empty_hint": "まだ会話はありません。受信したメッセージはここに表示されます。", "chat_empty_hint": "まだ会話はありません。受信したメッセージはここに表示されます。",
"chat_empty_start": "「新しい会話」から始めましょう。", "chat_empty_start": "「新しい会話」から始めましょう。",
@@ -154,21 +169,39 @@
"chat_len_over": "メッセージが長すぎます", "chat_len_over": "メッセージが長すぎます",
"chat_locked_hint": "チャットを読み込むにはウォレットのロックを解除してください。", "chat_locked_hint": "チャットを読み込むにはウォレットのロックを解除してください。",
"chat_mute": "ミュート", "chat_mute": "ミュート",
"chat_new_button": "新しい会話", "chat_new_button": "新しいチャット",
"chat_new_message": "メッセージ", "chat_new_message": "メッセージ",
"chat_new_message_toast": "新しい暗号化チャットメッセージ", "chat_new_message_toast": "新しい暗号化チャットメッセージ",
"chat_new_send": "リクエストを送信", "chat_new_send": "リクエストを送信",
"chat_new_title": "新しい会話", "chat_new_title": "新しいチャット",
"chat_new_zaddr": "宛先Zアドレス", "chat_new_zaddr": "宛先Zアドレス",
"chat_no_matches": "検索に一致する会話がありません。", "chat_no_matches": "検索に一致する会話がありません。",
"chat_no_z_contacts": "シールドアドレスの連絡先はまだありません", "chat_no_z_contacts": "シールドアドレスの連絡先はまだありません",
"chat_opt_bubble_accent": "吹き出しの色",
"chat_opt_bubble_style": "吹き出しスタイル",
"chat_opt_density": "メッセージ密度",
"chat_opt_emoji": "絵文字スタイル",
"chat_opt_enter_sends": "Enterで送信",
"chat_opt_font_size": "文字サイズ",
"chat_opt_global_clock": "全体の時刻形式",
"chat_opt_poll": "取得間隔",
"chat_opt_timestamp": "タイムスタンプ",
"chat_pick_contact": "連絡先から選択…", "chat_pick_contact": "連絡先から選択…",
"chat_rename": "連絡先の名前を変更",
"chat_rename_hint": "連絡先名",
"chat_renamed": "連絡先の名前を変更しました",
"chat_retry": "再送信", "chat_retry": "再送信",
"chat_search": "会話を検索", "chat_search": "会話を検索",
"chat_sec_appearance": "外観",
"chat_sec_messaging": "メッセージ",
"chat_select_hint": "表示する会話を選択してください。", "chat_select_hint": "表示する会話を選択してください。",
"chat_send": "送信", "chat_send": "送信",
"chat_send_failed": "未送信", "chat_send_failed": "未送信",
"chat_sending": "送信中…", "chat_sending": "送信中…",
"chat_settings_done": "完了",
"chat_settings_section": "チャットと連絡先",
"chat_settings_tip": "チャットのカスタマイズ",
"chat_settings_title": "チャット設定",
"chat_show_hidden": "非表示を表示", "chat_show_hidden": "非表示を表示",
"chat_time_now": "たった今", "chat_time_now": "たった今",
"chat_toast_compose_failed": "メッセージを作成できませんでした(長すぎませんか?)。", "chat_toast_compose_failed": "メッセージを作成できませんでした(長すぎませんか?)。",
@@ -179,9 +212,16 @@
"chat_toast_request_compose_failed": "連絡リクエストを作成できませんでした(アドレスまたはテキストが無効?)。", "chat_toast_request_compose_failed": "連絡リクエストを作成できませんでした(アドレスまたはテキストが無効?)。",
"chat_toast_request_queued": "連絡リクエストを送信待ちに追加しました。", "chat_toast_request_queued": "連絡リクエストを送信待ちに追加しました。",
"chat_toast_waiting_reply": "メッセージを送るには、この相手からの返信を待つ必要があります。", "chat_toast_waiting_reply": "メッセージを送るには、この相手からの返信を待つ必要があります。",
"chat_today": "今日",
"chat_ts_12h": "12時間",
"chat_ts_24h": "24時間",
"chat_ts_global": "全体設定に従う",
"chat_ts_global_short": "全体",
"chat_unhide": "再表示", "chat_unhide": "再表示",
"chat_unmute": "ミュート解除", "chat_unmute": "ミュート解除",
"chat_verify_key": "識別鍵 — 照合して確認",
"chat_waiting_reply": "この相手からの返信を待っています — 返信があればメッセージを送れます。", "chat_waiting_reply": "この相手からの返信を待っています — 返信があればメッセージを送れます。",
"chat_yesterday": "昨日",
"chat_you": "自分", "chat_you": "自分",
"choose_icon": "アイコンを選択", "choose_icon": "アイコンを選択",
"clear": "クリア", "clear": "クリア",
@@ -193,6 +233,7 @@
"click_copy_address": "クリックしてアドレスをコピー", "click_copy_address": "クリックしてアドレスをコピー",
"click_copy_uri": "クリックしてURIをコピー", "click_copy_uri": "クリックしてURIをコピー",
"click_to_copy": "クリックしてコピー", "click_to_copy": "クリックしてコピー",
"clock_format": "時刻形式",
"close": "閉じる", "close": "閉じる",
"conf_count": "%d 確認", "conf_count": "%d 確認",
"confirm_and_send": "確認して送金", "confirm_and_send": "確認して送金",
@@ -1385,6 +1426,7 @@
"tt_change_pass": "ウォレットの暗号化パスフレーズを変更", "tt_change_pass": "ウォレットの暗号化パスフレーズを変更",
"tt_change_pin": "アンロック PIN を変更", "tt_change_pin": "アンロック PIN を変更",
"tt_clear_ztx": "ローカルにキャッシュされた z-トランザクション履歴を削除", "tt_clear_ztx": "ローカルにキャッシュされた z-トランザクション履歴を削除",
"tt_clock_format": "24時間または12時間表示アプリ全体。チャットで上書きできます。",
"tt_custom_fees": "トランザクション送信時に手動手数料入力を有効化", "tt_custom_fees": "トランザクション送信時に手動手数料入力を有効化",
"tt_custom_theme": "カスタムテーマがアクティブ", "tt_custom_theme": "カスタムテーマがアクティブ",
"tt_daemon_install_bundled": "ノードを停止し、インストール済みの dragonxd をこのウォレットビルドにバンドルされたバージョンで上書きしてから再起動します", "tt_daemon_install_bundled": "ノードを停止し、インストール済みの dragonxd をこのウォレットビルドにバンドルされたバージョンで上書きしてから再起動します",

View File

@@ -135,10 +135,25 @@
"change_pass_title": "암호 변경", "change_pass_title": "암호 변경",
"characters": "문자", "characters": "문자",
"chat": "채팅", "chat": "채팅",
"chat_accent_amber": "황색",
"chat_accent_blue": "파랑",
"chat_accent_green": "초록",
"chat_accent_pink": "분홍",
"chat_accent_purple": "보라",
"chat_accent_theme": "테마",
"chat_add_contact": "연락처 추가", "chat_add_contact": "연락처 추가",
"chat_awaiting_key": "답장 대기 중",
"chat_bubble_minimal": "미니멀",
"chat_bubble_rounded": "둥근",
"chat_bubble_square": "각진",
"chat_cancel": "취소", "chat_cancel": "취소",
"chat_contact_added": "연락처 추가됨 — 연락처에서 이름을 변경하세요", "chat_contact_added": "연락처 추가됨 — 연락처에서 이름을 변경하세요",
"chat_contact_request": "연락 요청", "chat_contact_request": "연락 요청",
"chat_copy_address_tip": "클릭하여 주소 복사",
"chat_density_comfortable": "편안하게",
"chat_density_compact": "촘촘하게",
"chat_emoji_color": "컬러",
"chat_emoji_mono": "단색",
"chat_emoji_search": "이모지 검색", "chat_emoji_search": "이모지 검색",
"chat_empty_hint": "아직 대화가 없습니다. 받은 메시지가 여기에 표시됩니다.", "chat_empty_hint": "아직 대화가 없습니다. 받은 메시지가 여기에 표시됩니다.",
"chat_empty_start": "\"새 대화\"로 시작하세요.", "chat_empty_start": "\"새 대화\"로 시작하세요.",
@@ -154,21 +169,39 @@
"chat_len_over": "메시지가 너무 깁니다", "chat_len_over": "메시지가 너무 깁니다",
"chat_locked_hint": "채팅을 불러오려면 지갑 잠금을 해제하세요.", "chat_locked_hint": "채팅을 불러오려면 지갑 잠금을 해제하세요.",
"chat_mute": "음소거", "chat_mute": "음소거",
"chat_new_button": "새 대화", "chat_new_button": "새 채팅",
"chat_new_message": "메시지", "chat_new_message": "메시지",
"chat_new_message_toast": "새 암호화 채팅 메시지", "chat_new_message_toast": "새 암호화 채팅 메시지",
"chat_new_send": "요청 보내기", "chat_new_send": "요청 보내기",
"chat_new_title": "새 대화", "chat_new_title": "새 채팅",
"chat_new_zaddr": "받는 사람 z-주소", "chat_new_zaddr": "받는 사람 z-주소",
"chat_no_matches": "검색과 일치하는 대화가 없습니다.", "chat_no_matches": "검색과 일치하는 대화가 없습니다.",
"chat_no_z_contacts": "보호 주소 연락처가 아직 없습니다", "chat_no_z_contacts": "보호 주소 연락처가 아직 없습니다",
"chat_opt_bubble_accent": "말풍선 색상",
"chat_opt_bubble_style": "말풍선 스타일",
"chat_opt_density": "메시지 밀도",
"chat_opt_emoji": "이모지 스타일",
"chat_opt_enter_sends": "Enter로 전송",
"chat_opt_font_size": "글자 크기",
"chat_opt_global_clock": "전역 시간 형식",
"chat_opt_poll": "폴링 주기",
"chat_opt_timestamp": "타임스탬프",
"chat_pick_contact": "연락처에서 선택…", "chat_pick_contact": "연락처에서 선택…",
"chat_rename": "연락처 이름 변경",
"chat_rename_hint": "연락처 이름",
"chat_renamed": "연락처 이름이 변경되었습니다",
"chat_retry": "다시 시도", "chat_retry": "다시 시도",
"chat_search": "대화 검색", "chat_search": "대화 검색",
"chat_sec_appearance": "모양",
"chat_sec_messaging": "메시지",
"chat_select_hint": "볼 대화를 선택하세요.", "chat_select_hint": "볼 대화를 선택하세요.",
"chat_send": "전송", "chat_send": "전송",
"chat_send_failed": "전송 안 됨", "chat_send_failed": "전송 안 됨",
"chat_sending": "전송 중…", "chat_sending": "전송 중…",
"chat_settings_done": "완료",
"chat_settings_section": "채팅 및 연락처",
"chat_settings_tip": "채팅 사용자 지정",
"chat_settings_title": "채팅 설정",
"chat_show_hidden": "숨김 보기", "chat_show_hidden": "숨김 보기",
"chat_time_now": "방금", "chat_time_now": "방금",
"chat_toast_compose_failed": "메시지를 작성할 수 없습니다 (너무 긴가요?).", "chat_toast_compose_failed": "메시지를 작성할 수 없습니다 (너무 긴가요?).",
@@ -179,9 +212,16 @@
"chat_toast_request_compose_failed": "연락 요청을 작성할 수 없습니다 (잘못된 주소 / 텍스트?).", "chat_toast_request_compose_failed": "연락 요청을 작성할 수 없습니다 (잘못된 주소 / 텍스트?).",
"chat_toast_request_queued": "연락 요청이 대기열에 추가되었습니다.", "chat_toast_request_queued": "연락 요청이 대기열에 추가되었습니다.",
"chat_toast_waiting_reply": "상대방이 답장해야 메시지를 보낼 수 있습니다.", "chat_toast_waiting_reply": "상대방이 답장해야 메시지를 보낼 수 있습니다.",
"chat_today": "오늘",
"chat_ts_12h": "12시간",
"chat_ts_24h": "24시간",
"chat_ts_global": "전역 설정 따르기",
"chat_ts_global_short": "전역",
"chat_unhide": "다시 표시", "chat_unhide": "다시 표시",
"chat_unmute": "음소거 해제", "chat_unmute": "음소거 해제",
"chat_verify_key": "신원 키 — 비교하여 확인",
"chat_waiting_reply": "상대방의 답장을 기다리는 중입니다 — 답장하면 메시지를 보낼 수 있습니다.", "chat_waiting_reply": "상대방의 답장을 기다리는 중입니다 — 답장하면 메시지를 보낼 수 있습니다.",
"chat_yesterday": "어제",
"chat_you": "나", "chat_you": "나",
"choose_icon": "아이콘 선택", "choose_icon": "아이콘 선택",
"clear": "지우기", "clear": "지우기",
@@ -193,6 +233,7 @@
"click_copy_address": "클릭하여 주소 복사", "click_copy_address": "클릭하여 주소 복사",
"click_copy_uri": "클릭하여 URI 복사", "click_copy_uri": "클릭하여 URI 복사",
"click_to_copy": "복사하려면 클릭", "click_to_copy": "복사하려면 클릭",
"clock_format": "시간 형식",
"close": "닫기", "close": "닫기",
"conf_count": "%d 확인", "conf_count": "%d 확인",
"confirm_and_send": "확인 후 전송", "confirm_and_send": "확인 후 전송",
@@ -1385,6 +1426,7 @@
"tt_change_pass": "지갑 암호화 비밀번호 변경", "tt_change_pass": "지갑 암호화 비밀번호 변경",
"tt_change_pin": "잠금 해제 PIN 변경", "tt_change_pin": "잠금 해제 PIN 변경",
"tt_clear_ztx": "로컬에 캐시된 z-트랜잭션 기록 삭제", "tt_clear_ztx": "로컬에 캐시된 z-트랜잭션 기록 삭제",
"tt_clock_format": "24시간 또는 12시간 형식(앱 전체). 채팅에서 재정의할 수 있습니다.",
"tt_custom_fees": "거래 전송 시 수동 수수료 입력 활성화", "tt_custom_fees": "거래 전송 시 수동 수수료 입력 활성화",
"tt_custom_theme": "사용자 지정 테마 활성화됨", "tt_custom_theme": "사용자 지정 테마 활성화됨",
"tt_daemon_install_bundled": "노드를 중지하고 설치된 dragonxd를 이 지갑 빌드에 번들된 버전으로 덮어쓴 다음 재시작합니다", "tt_daemon_install_bundled": "노드를 중지하고 설치된 dragonxd를 이 지갑 빌드에 번들된 버전으로 덮어쓴 다음 재시작합니다",

View File

@@ -135,10 +135,25 @@
"change_pass_title": "Alterar senha", "change_pass_title": "Alterar senha",
"characters": "caracteres", "characters": "caracteres",
"chat": "Chat", "chat": "Chat",
"chat_accent_amber": "Âmbar",
"chat_accent_blue": "Azul",
"chat_accent_green": "Verde",
"chat_accent_pink": "Rosa",
"chat_accent_purple": "Roxo",
"chat_accent_theme": "Tema",
"chat_add_contact": "Adicionar contato", "chat_add_contact": "Adicionar contato",
"chat_awaiting_key": "Aguardando resposta",
"chat_bubble_minimal": "Mínimo",
"chat_bubble_rounded": "Arredondado",
"chat_bubble_square": "Quadrado",
"chat_cancel": "Cancelar", "chat_cancel": "Cancelar",
"chat_contact_added": "Contato adicionado — renomeie em Contatos", "chat_contact_added": "Contato adicionado — renomeie em Contatos",
"chat_contact_request": "solicitação de contato", "chat_contact_request": "solicitação de contato",
"chat_copy_address_tip": "Clique para copiar o endereço",
"chat_density_comfortable": "Confortável",
"chat_density_compact": "Compacta",
"chat_emoji_color": "Colorido",
"chat_emoji_mono": "Monocromático",
"chat_emoji_search": "Pesquisar emoji", "chat_emoji_search": "Pesquisar emoji",
"chat_empty_hint": "Nenhuma conversa ainda. As mensagens que você receber aparecerão aqui.", "chat_empty_hint": "Nenhuma conversa ainda. As mensagens que você receber aparecerão aqui.",
"chat_empty_start": "Inicie uma com \"Nova conversa\".", "chat_empty_start": "Inicie uma com \"Nova conversa\".",
@@ -154,21 +169,39 @@
"chat_len_over": "Mensagem muito longa", "chat_len_over": "Mensagem muito longa",
"chat_locked_hint": "Desbloqueie sua carteira para carregar suas conversas.", "chat_locked_hint": "Desbloqueie sua carteira para carregar suas conversas.",
"chat_mute": "Silenciar", "chat_mute": "Silenciar",
"chat_new_button": "Nova conversa", "chat_new_button": "Novo chat",
"chat_new_message": "Mensagem", "chat_new_message": "Mensagem",
"chat_new_message_toast": "Nova mensagem de chat criptografada", "chat_new_message_toast": "Nova mensagem de chat criptografada",
"chat_new_send": "Enviar solicitação", "chat_new_send": "Enviar solicitação",
"chat_new_title": "Nova conversa", "chat_new_title": "Novo chat",
"chat_new_zaddr": "Endereço-z do destinatário", "chat_new_zaddr": "Endereço-z do destinatário",
"chat_no_matches": "Nenhuma conversa corresponde à sua pesquisa.", "chat_no_matches": "Nenhuma conversa corresponde à sua pesquisa.",
"chat_no_z_contacts": "Ainda não há contatos com endereço blindado", "chat_no_z_contacts": "Ainda não há contatos com endereço blindado",
"chat_opt_bubble_accent": "Cor do balão",
"chat_opt_bubble_style": "Estilo do balão",
"chat_opt_density": "Densidade das mensagens",
"chat_opt_emoji": "Estilo de emoji",
"chat_opt_enter_sends": "Enter envia a mensagem",
"chat_opt_font_size": "Tamanho do texto",
"chat_opt_global_clock": "Formato de relógio global",
"chat_opt_poll": "Taxa de atualização",
"chat_opt_timestamp": "Carimbos de data/hora",
"chat_pick_contact": "Escolher dos contatos…", "chat_pick_contact": "Escolher dos contatos…",
"chat_rename": "Renomear contato",
"chat_rename_hint": "Nome do contato",
"chat_renamed": "Contato renomeado",
"chat_retry": "Tentar novamente", "chat_retry": "Tentar novamente",
"chat_search": "Pesquisar conversas", "chat_search": "Pesquisar conversas",
"chat_sec_appearance": "APARÊNCIA",
"chat_sec_messaging": "MENSAGENS",
"chat_select_hint": "Selecione uma conversa para visualizá-la.", "chat_select_hint": "Selecione uma conversa para visualizá-la.",
"chat_send": "Enviar", "chat_send": "Enviar",
"chat_send_failed": "não enviada", "chat_send_failed": "não enviada",
"chat_sending": "enviando…", "chat_sending": "enviando…",
"chat_settings_done": "Concluído",
"chat_settings_section": "CHAT E CONTATOS",
"chat_settings_tip": "Personalizar chat",
"chat_settings_title": "Configurações de chat",
"chat_show_hidden": "Ver ocultas", "chat_show_hidden": "Ver ocultas",
"chat_time_now": "agora", "chat_time_now": "agora",
"chat_toast_compose_failed": "Não foi possível compor a mensagem (muito longa?).", "chat_toast_compose_failed": "Não foi possível compor a mensagem (muito longa?).",
@@ -179,9 +212,16 @@
"chat_toast_request_compose_failed": "Não foi possível compor a solicitação de contato (endereço / texto inválido?).", "chat_toast_request_compose_failed": "Não foi possível compor a solicitação de contato (endereço / texto inválido?).",
"chat_toast_request_queued": "Solicitação de contato na fila.", "chat_toast_request_queued": "Solicitação de contato na fila.",
"chat_toast_waiting_reply": "Aguardando a resposta do contato antes que você possa enviar mensagens a ele.", "chat_toast_waiting_reply": "Aguardando a resposta do contato antes que você possa enviar mensagens a ele.",
"chat_today": "Hoje",
"chat_ts_12h": "12 horas",
"chat_ts_24h": "24 horas",
"chat_ts_global": "Seguir global",
"chat_ts_global_short": "Global",
"chat_unhide": "Mostrar", "chat_unhide": "Mostrar",
"chat_unmute": "Reativar som", "chat_unmute": "Reativar som",
"chat_verify_key": "Chave de identidade — compare para verificar",
"chat_waiting_reply": "Aguardando a resposta deste contato — você poderá enviar mensagens assim que ele responder.", "chat_waiting_reply": "Aguardando a resposta deste contato — você poderá enviar mensagens assim que ele responder.",
"chat_yesterday": "Ontem",
"chat_you": "Você", "chat_you": "Você",
"choose_icon": "Escolher Ícone", "choose_icon": "Escolher Ícone",
"clear": "Limpar", "clear": "Limpar",
@@ -193,6 +233,7 @@
"click_copy_address": "Clique para copiar o endereço", "click_copy_address": "Clique para copiar o endereço",
"click_copy_uri": "Clique para copiar a URI", "click_copy_uri": "Clique para copiar a URI",
"click_to_copy": "Clique para copiar", "click_to_copy": "Clique para copiar",
"clock_format": "Formato de hora",
"close": "Fechar", "close": "Fechar",
"conf_count": "%d conf.", "conf_count": "%d conf.",
"confirm_and_send": "Confirmar & Enviar", "confirm_and_send": "Confirmar & Enviar",
@@ -1385,6 +1426,7 @@
"tt_change_pass": "Alterar a frase secreta de encriptação da carteira", "tt_change_pass": "Alterar a frase secreta de encriptação da carteira",
"tt_change_pin": "Alterar seu PIN de desbloqueio", "tt_change_pin": "Alterar seu PIN de desbloqueio",
"tt_clear_ztx": "Excluir histórico de z-transações em cache local", "tt_clear_ztx": "Excluir histórico de z-transações em cache local",
"tt_clock_format": "Relógio de 24 ou 12 horas, em todo o app. O chat pode substituí-lo.",
"tt_custom_fees": "Ativar entrada manual de taxas ao enviar transações", "tt_custom_fees": "Ativar entrada manual de taxas ao enviar transações",
"tt_custom_theme": "Tema personalizado ativo", "tt_custom_theme": "Tema personalizado ativo",
"tt_daemon_install_bundled": "Parar o nó, sobrescrever o dragonxd instalado com a versão incluída nesta compilação da carteira e reiniciar", "tt_daemon_install_bundled": "Parar o nó, sobrescrever o dragonxd instalado com a versão incluída nesta compilação da carteira e reiniciar",

View File

@@ -135,10 +135,25 @@
"change_pass_title": "Сменить пароль", "change_pass_title": "Сменить пароль",
"characters": "символов", "characters": "символов",
"chat": "Чат", "chat": "Чат",
"chat_accent_amber": "Янтарный",
"chat_accent_blue": "Синий",
"chat_accent_green": "Зелёный",
"chat_accent_pink": "Розовый",
"chat_accent_purple": "Фиолетовый",
"chat_accent_theme": "Тема",
"chat_add_contact": "Добавить контакт", "chat_add_contact": "Добавить контакт",
"chat_awaiting_key": "Ожидание ответа",
"chat_bubble_minimal": "Минимальный",
"chat_bubble_rounded": "Скруглённый",
"chat_bubble_square": "Прямоугольный",
"chat_cancel": "Отмена", "chat_cancel": "Отмена",
"chat_contact_added": "Контакт добавлен — переименуйте его в Контактах", "chat_contact_added": "Контакт добавлен — переименуйте его в Контактах",
"chat_contact_request": "запрос контакта", "chat_contact_request": "запрос контакта",
"chat_copy_address_tip": "Нажмите, чтобы скопировать адрес",
"chat_density_comfortable": "Свободная",
"chat_density_compact": "Компактная",
"chat_emoji_color": "Цветной",
"chat_emoji_mono": "Монохромный",
"chat_emoji_search": "Поиск эмодзи", "chat_emoji_search": "Поиск эмодзи",
"chat_empty_hint": "Пока нет переписок. Полученные сообщения появятся здесь.", "chat_empty_hint": "Пока нет переписок. Полученные сообщения появятся здесь.",
"chat_empty_start": "Начните новый с помощью «Новый разговор».", "chat_empty_start": "Начните новый с помощью «Новый разговор».",
@@ -154,21 +169,39 @@
"chat_len_over": "Сообщение слишком длинное", "chat_len_over": "Сообщение слишком длинное",
"chat_locked_hint": "Разблокируйте кошелёк, чтобы загрузить переписку.", "chat_locked_hint": "Разблокируйте кошелёк, чтобы загрузить переписку.",
"chat_mute": "Отключить уведомления", "chat_mute": "Отключить уведомления",
"chat_new_button": "Новая переписка", "chat_new_button": "Новый чат",
"chat_new_message": "Сообщение", "chat_new_message": "Сообщение",
"chat_new_message_toast": "Новое зашифрованное сообщение", "chat_new_message_toast": "Новое зашифрованное сообщение",
"chat_new_send": "Отправить запрос", "chat_new_send": "Отправить запрос",
"chat_new_title": "Новая переписка", "chat_new_title": "Новый чат",
"chat_new_zaddr": "Z-адрес получателя", "chat_new_zaddr": "Z-адрес получателя",
"chat_no_matches": "Нет разговоров, соответствующих запросу.", "chat_no_matches": "Нет разговоров, соответствующих запросу.",
"chat_no_z_contacts": "Пока нет контактов с защищённым адресом", "chat_no_z_contacts": "Пока нет контактов с защищённым адресом",
"chat_opt_bubble_accent": "Цвет пузырька",
"chat_opt_bubble_style": "Стиль пузырька",
"chat_opt_density": "Плотность сообщений",
"chat_opt_emoji": "Стиль эмодзи",
"chat_opt_enter_sends": "Enter отправляет сообщение",
"chat_opt_font_size": "Размер текста",
"chat_opt_global_clock": "Глобальный формат времени",
"chat_opt_poll": "Частота опроса",
"chat_opt_timestamp": "Метки времени",
"chat_pick_contact": "Выбрать из контактов…", "chat_pick_contact": "Выбрать из контактов…",
"chat_rename": "Переименовать контакт",
"chat_rename_hint": "Имя контакта",
"chat_renamed": "Контакт переименован",
"chat_retry": "Повторить", "chat_retry": "Повторить",
"chat_search": "Поиск разговоров", "chat_search": "Поиск разговоров",
"chat_sec_appearance": "ВИД",
"chat_sec_messaging": "СООБЩЕНИЯ",
"chat_select_hint": "Выберите переписку для просмотра.", "chat_select_hint": "Выберите переписку для просмотра.",
"chat_send": "Отправить", "chat_send": "Отправить",
"chat_send_failed": "не отправлено", "chat_send_failed": "не отправлено",
"chat_sending": "отправка…", "chat_sending": "отправка…",
"chat_settings_done": "Готово",
"chat_settings_section": "ЧАТ И КОНТАКТЫ",
"chat_settings_tip": "Настройка чата",
"chat_settings_title": "Настройки чата",
"chat_show_hidden": "Показать скрытые", "chat_show_hidden": "Показать скрытые",
"chat_time_now": "сейчас", "chat_time_now": "сейчас",
"chat_toast_compose_failed": "Не удалось составить сообщение (слишком длинное?).", "chat_toast_compose_failed": "Не удалось составить сообщение (слишком длинное?).",
@@ -179,9 +212,16 @@
"chat_toast_request_compose_failed": "Не удалось составить запрос контакта (неверный адрес / текст?).", "chat_toast_request_compose_failed": "Не удалось составить запрос контакта (неверный адрес / текст?).",
"chat_toast_request_queued": "Запрос контакта поставлен в очередь.", "chat_toast_request_queued": "Запрос контакта поставлен в очередь.",
"chat_toast_waiting_reply": "Ожидание ответа от контакта — вы сможете писать ему только после этого.", "chat_toast_waiting_reply": "Ожидание ответа от контакта — вы сможете писать ему только после этого.",
"chat_today": "Сегодня",
"chat_ts_12h": "12 часов",
"chat_ts_24h": "24 часа",
"chat_ts_global": "Как глобально",
"chat_ts_global_short": "Общий",
"chat_unhide": "Показать", "chat_unhide": "Показать",
"chat_unmute": "Включить уведомления", "chat_unmute": "Включить уведомления",
"chat_verify_key": "Ключ личности — сравните для проверки",
"chat_waiting_reply": "Ожидание ответа от контакта — вы сможете писать ему, как только он ответит.", "chat_waiting_reply": "Ожидание ответа от контакта — вы сможете писать ему, как только он ответит.",
"chat_yesterday": "Вчера",
"chat_you": "Вы", "chat_you": "Вы",
"choose_icon": "Выбрать иконку", "choose_icon": "Выбрать иконку",
"clear": "Очистить", "clear": "Очистить",
@@ -193,6 +233,7 @@
"click_copy_address": "Нажмите, чтобы скопировать адрес", "click_copy_address": "Нажмите, чтобы скопировать адрес",
"click_copy_uri": "Нажмите, чтобы скопировать URI", "click_copy_uri": "Нажмите, чтобы скопировать URI",
"click_to_copy": "Нажмите для копирования", "click_to_copy": "Нажмите для копирования",
"clock_format": "Формат времени",
"close": "Закрыть", "close": "Закрыть",
"conf_count": "%d подтв.", "conf_count": "%d подтв.",
"confirm_and_send": "Подтвердить и отправить", "confirm_and_send": "Подтвердить и отправить",
@@ -1385,6 +1426,7 @@
"tt_change_pass": "Сменить пароль шифрования кошелька", "tt_change_pass": "Сменить пароль шифрования кошелька",
"tt_change_pin": "Изменить PIN-код разблокировки", "tt_change_pin": "Изменить PIN-код разблокировки",
"tt_clear_ztx": "Удалить локально кешированную историю z-транзакций", "tt_clear_ztx": "Удалить локально кешированную историю z-транзакций",
"tt_clock_format": "24- или 12-часовой формат для всего приложения. Чат может переопределить.",
"tt_custom_fees": "Включить ручной ввод комиссий при отправке транзакций", "tt_custom_fees": "Включить ручной ввод комиссий при отправке транзакций",
"tt_custom_theme": "Пользовательская тема активна", "tt_custom_theme": "Пользовательская тема активна",
"tt_daemon_install_bundled": "Остановить узел, перезаписать установленный dragonxd версией, встроенной в эту сборку кошелька, затем перезапустить", "tt_daemon_install_bundled": "Остановить узел, перезаписать установленный dragonxd версией, встроенной в эту сборку кошелька, затем перезапустить",

View File

@@ -135,10 +135,25 @@
"change_pass_title": "更改密码短语", "change_pass_title": "更改密码短语",
"characters": "字符", "characters": "字符",
"chat": "聊天", "chat": "聊天",
"chat_accent_amber": "琥珀色",
"chat_accent_blue": "蓝色",
"chat_accent_green": "绿色",
"chat_accent_pink": "粉色",
"chat_accent_purple": "紫色",
"chat_accent_theme": "主题",
"chat_add_contact": "添加联系人", "chat_add_contact": "添加联系人",
"chat_awaiting_key": "等待回复",
"chat_bubble_minimal": "极简",
"chat_bubble_rounded": "圆角",
"chat_bubble_square": "方形",
"chat_cancel": "取消", "chat_cancel": "取消",
"chat_contact_added": "已添加联系人——可在联系人中重命名", "chat_contact_added": "已添加联系人——可在联系人中重命名",
"chat_contact_request": "联系人请求", "chat_contact_request": "联系人请求",
"chat_copy_address_tip": "点击复制地址",
"chat_density_comfortable": "宽松",
"chat_density_compact": "紧凑",
"chat_emoji_color": "彩色",
"chat_emoji_mono": "单色",
"chat_emoji_search": "搜索表情", "chat_emoji_search": "搜索表情",
"chat_empty_hint": "暂无对话。您收到的消息将显示在此处。", "chat_empty_hint": "暂无对话。您收到的消息将显示在此处。",
"chat_empty_start": "点击\"新建会话\"开始。", "chat_empty_start": "点击\"新建会话\"开始。",
@@ -154,21 +169,39 @@
"chat_len_over": "消息过长", "chat_len_over": "消息过长",
"chat_locked_hint": "解锁钱包以加载您的聊天记录。", "chat_locked_hint": "解锁钱包以加载您的聊天记录。",
"chat_mute": "静音", "chat_mute": "静音",
"chat_new_button": "新建对话", "chat_new_button": "新聊天",
"chat_new_message": "消息", "chat_new_message": "消息",
"chat_new_message_toast": "新的加密聊天消息", "chat_new_message_toast": "新的加密聊天消息",
"chat_new_send": "发送请求", "chat_new_send": "发送请求",
"chat_new_title": "新建对话", "chat_new_title": "新聊天",
"chat_new_zaddr": "收款方 z 地址", "chat_new_zaddr": "收款方 z 地址",
"chat_no_matches": "没有与搜索匹配的会话。", "chat_no_matches": "没有与搜索匹配的会话。",
"chat_no_z_contacts": "暂无使用隐私地址的联系人", "chat_no_z_contacts": "暂无使用隐私地址的联系人",
"chat_opt_bubble_accent": "气泡颜色",
"chat_opt_bubble_style": "气泡样式",
"chat_opt_density": "消息密度",
"chat_opt_emoji": "表情样式",
"chat_opt_enter_sends": "回车发送消息",
"chat_opt_font_size": "文字大小",
"chat_opt_global_clock": "全局时间格式",
"chat_opt_poll": "轮询频率",
"chat_opt_timestamp": "时间戳",
"chat_pick_contact": "从联系人中选择…", "chat_pick_contact": "从联系人中选择…",
"chat_rename": "重命名联系人",
"chat_rename_hint": "联系人名称",
"chat_renamed": "联系人已重命名",
"chat_retry": "重试", "chat_retry": "重试",
"chat_search": "搜索会话", "chat_search": "搜索会话",
"chat_sec_appearance": "外观",
"chat_sec_messaging": "消息",
"chat_select_hint": "选择一个对话以查看。", "chat_select_hint": "选择一个对话以查看。",
"chat_send": "发送", "chat_send": "发送",
"chat_send_failed": "未发送", "chat_send_failed": "未发送",
"chat_sending": "发送中…", "chat_sending": "发送中…",
"chat_settings_done": "完成",
"chat_settings_section": "聊天与联系人",
"chat_settings_tip": "聊天自定义",
"chat_settings_title": "聊天设置",
"chat_show_hidden": "显示已隐藏", "chat_show_hidden": "显示已隐藏",
"chat_time_now": "刚刚", "chat_time_now": "刚刚",
"chat_toast_compose_failed": "无法编写该消息(内容过长?)。", "chat_toast_compose_failed": "无法编写该消息(内容过长?)。",
@@ -179,9 +212,16 @@
"chat_toast_request_compose_failed": "无法编写联系人请求(地址或文本无效?)。", "chat_toast_request_compose_failed": "无法编写联系人请求(地址或文本无效?)。",
"chat_toast_request_queued": "联系人请求已排队。", "chat_toast_request_queued": "联系人请求已排队。",
"chat_toast_waiting_reply": "等待该联系人回复——对方回复后您即可向其发送消息。", "chat_toast_waiting_reply": "等待该联系人回复——对方回复后您即可向其发送消息。",
"chat_today": "今天",
"chat_ts_12h": "12小时",
"chat_ts_24h": "24小时",
"chat_ts_global": "跟随全局",
"chat_ts_global_short": "全局",
"chat_unhide": "取消隐藏", "chat_unhide": "取消隐藏",
"chat_unmute": "取消静音", "chat_unmute": "取消静音",
"chat_verify_key": "身份密钥 — 对比以验证",
"chat_waiting_reply": "等待该联系人回复——对方回复后您即可向其发送消息。", "chat_waiting_reply": "等待该联系人回复——对方回复后您即可向其发送消息。",
"chat_yesterday": "昨天",
"chat_you": "我", "chat_you": "我",
"choose_icon": "选择图标", "choose_icon": "选择图标",
"clear": "清除", "clear": "清除",
@@ -193,6 +233,7 @@
"click_copy_address": "点击复制地址", "click_copy_address": "点击复制地址",
"click_copy_uri": "点击复制 URI", "click_copy_uri": "点击复制 URI",
"click_to_copy": "点击复制", "click_to_copy": "点击复制",
"clock_format": "时间格式",
"close": "关闭", "close": "关闭",
"conf_count": "%d 确认", "conf_count": "%d 确认",
"confirm_and_send": "确认并发送", "confirm_and_send": "确认并发送",
@@ -1385,6 +1426,7 @@
"tt_change_pass": "更改钱包加密密码", "tt_change_pass": "更改钱包加密密码",
"tt_change_pin": "更改您的解锁 PIN", "tt_change_pin": "更改您的解锁 PIN",
"tt_clear_ztx": "删除本地缓存的 z-交易历史", "tt_clear_ztx": "删除本地缓存的 z-交易历史",
"tt_clock_format": "24 或 12 小时制,应用全局。聊天可覆盖。",
"tt_custom_fees": "发送交易时启用手动费用输入", "tt_custom_fees": "发送交易时启用手动费用输入",
"tt_custom_theme": "自定义主题已激活", "tt_custom_theme": "自定义主题已激活",
"tt_daemon_install_bundled": "停止节点,用此钱包版本内置的 dragonxd 覆盖已安装的版本,然后重启", "tt_daemon_install_bundled": "停止节点,用此钱包版本内置的 dragonxd 覆盖已安装的版本,然后重启",

94
scripts/build-freetype-mingw.sh Executable file
View File

@@ -0,0 +1,94 @@
#!/usr/bin/env bash
# Cross-build a MINIMAL static FreeType for the mingw-w64 (Windows) target.
#
# Why: the wallet's optional color-emoji rendering needs FreeType (to rasterize the COLR/CPAL Twemoji
# font). Native Linux/macOS pick up the system FreeType via find_package; the Debian/Ubuntu mingw-w64
# cross toolchain ships no FreeType, so we build one here. The Twemoji font is COLRv0 (layered vector),
# which FreeType renders WITHOUT libpng / harfbuzz / brotli / zlib — so this is a dependency-free static
# build (no external libs to also cross-compile), producing a self-contained libfreetype.a.
#
# Output: <prefix>/include/freetype2/... + <prefix>/lib/libfreetype.a (default prefix: third_party/freetype-mingw)
# build.sh --win-release runs this automatically and passes -DDRAGONX_MINGW_FREETYPE_PREFIX to CMake.
set -euo pipefail
FT_VERSION="2.13.3"
FT_SHA256="5c3a8e78f7b24c20b25b54ee575d6daa40007a5f4eea2845861c3409b3021747" # freetype-2.13.3.tar.gz
FT_URL="https://download.savannah.gnu.org/releases/freetype/freetype-${FT_VERSION}.tar.gz"
FT_URL_MIRROR="https://downloads.sourceforge.net/project/freetype/freetype2/${FT_VERSION}/freetype-${FT_VERSION}.tar.gz"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
PREFIX="${1:-$SCRIPT_DIR/third_party/freetype-mingw}"
WORK="$SCRIPT_DIR/third_party/.freetype-mingw-build"
# Already built? (libfreetype.a present) → nothing to do.
if [[ -f "$PREFIX/lib/libfreetype.a" && -d "$PREFIX/include/freetype2" ]]; then
echo "FreeType (mingw) already built at: $PREFIX"
exit 0
fi
# Pick the mingw compilers (posix threads variant preferred, matching build.sh).
if command -v x86_64-w64-mingw32-gcc-posix &>/dev/null; then
MINGW_GCC=x86_64-w64-mingw32-gcc-posix; MINGW_GXX=x86_64-w64-mingw32-g++-posix
elif command -v x86_64-w64-mingw32-gcc &>/dev/null; then
MINGW_GCC=x86_64-w64-mingw32-gcc; MINGW_GXX=x86_64-w64-mingw32-g++
else
echo "ERROR: x86_64-w64-mingw32-gcc not found (install mingw-w64)." >&2
exit 1
fi
mkdir -p "$WORK"
cd "$WORK"
TARBALL="freetype-${FT_VERSION}.tar.gz"
if [[ ! -f "$TARBALL" ]]; then
echo "Downloading FreeType ${FT_VERSION} ..."
curl -fsSL -o "$TARBALL" "$FT_URL" || curl -fsSL -o "$TARBALL" "$FT_URL_MIRROR"
fi
echo "Verifying SHA-256 ..."
echo "${FT_SHA256} ${TARBALL}" | sha256sum -c - || {
echo "ERROR: FreeType tarball checksum mismatch (expected ${FT_SHA256})." >&2
echo " got: $(sha256sum "$TARBALL" | cut -d' ' -f1)" >&2
exit 1
}
rm -rf "freetype-${FT_VERSION}"
tar xf "$TARBALL"
SRC="$WORK/freetype-${FT_VERSION}"
# Minimal mingw toolchain for FreeType's own CMake.
cat > "$WORK/ft-mingw-toolchain.cmake" <<TOOLCHAIN
set(CMAKE_SYSTEM_NAME Windows)
set(CMAKE_SYSTEM_PROCESSOR x86_64)
set(CMAKE_C_COMPILER ${MINGW_GCC})
set(CMAKE_CXX_COMPILER ${MINGW_GXX})
set(CMAKE_RC_COMPILER x86_64-w64-mingw32-windres)
set(CMAKE_FIND_ROOT_PATH /usr/x86_64-w64-mingw32)
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
TOOLCHAIN
echo "Configuring FreeType (static, no external deps) ..."
rm -rf "$WORK/build"
cmake -S "$SRC" -B "$WORK/build" \
-DCMAKE_TOOLCHAIN_FILE="$WORK/ft-mingw-toolchain.cmake" \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_INSTALL_PREFIX="$PREFIX" \
-DBUILD_SHARED_LIBS=OFF \
-DFT_DISABLE_ZLIB=ON \
-DFT_DISABLE_BZIP2=ON \
-DFT_DISABLE_PNG=ON \
-DFT_DISABLE_HARFBUZZ=ON \
-DFT_DISABLE_BROTLI=ON
echo "Building + installing FreeType ..."
cmake --build "$WORK/build" -j "$(nproc)"
cmake --install "$WORK/build"
if [[ -f "$PREFIX/lib/libfreetype.a" ]]; then
echo "OK: mingw FreeType -> $PREFIX/lib/libfreetype.a"
else
echo "ERROR: build did not produce libfreetype.a" >&2
exit 1
fi

View File

@@ -293,6 +293,9 @@ bool App::init()
if (!settings_->load()) { if (!settings_->load()) {
DEBUG_LOGF("Warning: Could not load settings, using defaults\n"); DEBUG_LOGF("Warning: Could not load settings, using defaults\n");
} }
// The initial font atlas is built (in main, before App renders) with monochrome emoji. If the saved
// setting wants color, request a rebuild so the first preFrame switches to the FreeType color atlas.
if (settings_->getChatEmojiColor()) font_rebuild_requested_ = true;
// On upgrade (version mismatch), re-save to persist new defaults + current version // On upgrade (version mismatch), re-save to persist new defaults + current version
if (settings_->needsUpgradeSave()) { if (settings_->needsUpgradeSave()) {
DEBUG_LOGF("[INFO] Wallet upgraded — re-saving settings with new defaults\n"); DEBUG_LOGF("[INFO] Wallet upgraded — re-saving settings with new defaults\n");
@@ -532,6 +535,21 @@ void App::preFrame()
DEBUG_LOGF("App: Font atlas rebuilt after user font-scale change (%.1fx)\n", DEBUG_LOGF("App: Font atlas rebuilt after user font-scale change (%.1fx)\n",
ui::Layout::userFontScale()); ui::Layout::userFontScale());
} }
// Keep Typography's emoji-style flag in sync with the setting so any reload (font-scale, DPI, or the
// explicit request below) picks up the right emoji font. Inert unless this is a FreeType build.
if (settings_) ui::material::Typography::instance().setColorEmoji(settings_->getChatEmojiColor());
// App-wide clock format (24h/12h) — drives every user-facing timestamp via util::formatClock*.
if (settings_) util::setClock12h(settings_->getTimeFormat() == 1);
// Explicit rebuild request (e.g. the chat color-emoji toggle) — reload picks up the new emoji style.
if (font_rebuild_requested_) {
font_rebuild_requested_ = false;
auto& typo = ui::material::Typography::instance();
typo.reload(io, typo.getDpiScale());
DEBUG_LOGF("App: Font atlas rebuilt on request (chat emoji style)\n");
}
} }
namespace { namespace {
@@ -1174,7 +1192,8 @@ void App::update()
// normal harvest only re-scans on a new block. Self-gated (no-op without a chat identity / // normal harvest only re-scans on a new block. Self-gated (no-op without a chat identity /
// connection / on lite); the in-flight guard keeps overlapping RPCs from stacking. // connection / on lite); the in-flight guard keeps overlapping RPCs from stacking.
chat_fast_scan_accum_ += ImGui::GetIO().DeltaTime; chat_fast_scan_accum_ += ImGui::GetIO().DeltaTime;
if (chat_fast_scan_accum_ >= 2.5f) { const float chatPoll = settings_ ? settings_->getChatPollRateSec() : 2.5f; // user-configurable
if (chat_fast_scan_accum_ >= chatPoll) {
chat_fast_scan_accum_ = 0.0f; chat_fast_scan_accum_ = 0.0f;
fastScanChatMemos(); fastScanChatMemos();
} }

View File

@@ -171,6 +171,9 @@ public:
daemon::EmbeddedDaemon* consoleDaemon(); daemon::EmbeddedDaemon* consoleDaemon();
daemon::XmrigManager* consoleXmrig(); daemon::XmrigManager* consoleXmrig();
config::Settings* settings() { return settings_.get(); } config::Settings* settings() { return settings_.get(); }
// Request a font-atlas rebuild before the next frame (e.g. after toggling color emoji). Handled in
// preFrame() via Typography::reload — safe to call from UI code mid-frame.
void requestFontRebuild() { font_rebuild_requested_ = true; }
// Lite wallet controller (non-null only in lite builds with a linked backend). // Lite wallet controller (non-null only in lite builds with a linked backend).
wallet::LiteWalletController* liteWallet() { return lite_wallet_.get(); } wallet::LiteWalletController* liteWallet() { return lite_wallet_.get(); }
// HushChat service (identity + in-memory message store); the Chat tab reads its store. // HushChat service (identity + in-memory message store); the Chat tab reads its store.
@@ -736,6 +739,7 @@ private:
void fastScanChatMemos(); void fastScanChatMemos();
bool chat_fast_scan_in_flight_ = false; // guard against overlapping fast-scan RPCs bool chat_fast_scan_in_flight_ = false; // guard against overlapping fast-scan RPCs
float chat_fast_scan_accum_ = 0.0f; // seconds since the last fast-scan (dedicated ~2.5s poll) float chat_fast_scan_accum_ = 0.0f; // seconds since the last fast-scan (dedicated ~2.5s poll)
bool font_rebuild_requested_ = false; // set by requestFontRebuild(); consumed in preFrame()
// Lite first-run welcome prompt: dismissed for the session once the user picks an action. // Lite first-run welcome prompt: dismissed for the session once the user picks an action.
bool lite_firstrun_dismissed_ = false; bool lite_firstrun_dismissed_ = false;
// Lite send-time unlock: set to show the unlock modal when a spend is attempted while locked. // Lite send-time unlock: set to show the unlock modal when a spend is attempted while locked.

View File

@@ -157,6 +157,16 @@ bool Settings::load(const std::string& path)
for (const auto& c : j["hidden_chat_cids"]) for (const auto& c : j["hidden_chat_cids"])
if (c.is_string()) hidden_chat_cids_.push_back(c.get<std::string>()); if (c.is_string()) hidden_chat_cids_.push_back(c.get<std::string>());
} }
// Chat-tab customization (re-clamped through the setters so hand-edited JSON stays in range).
loadScalar(j, "chat_emoji_color", chat_emoji_color_);
loadScalar(j, "chat_poll_rate_sec", chat_poll_rate_sec_); setChatPollRateSec(chat_poll_rate_sec_);
loadScalar(j, "chat_bubble_style", chat_bubble_style_); setChatBubbleStyle(chat_bubble_style_);
loadScalar(j, "chat_bubble_accent", chat_bubble_accent_); setChatBubbleAccent(chat_bubble_accent_);
loadScalar(j, "chat_density", chat_density_); setChatDensity(chat_density_);
loadScalar(j, "chat_font_scale", chat_font_scale_); setChatFontScale(chat_font_scale_);
loadScalar(j, "chat_time_format", chat_time_format_); setChatTimeFormat(chat_time_format_);
loadScalar(j, "chat_enter_sends", chat_enter_sends_);
loadScalar(j, "time_format", time_format_); setTimeFormat(time_format_);
loadScalar(j, "acrylic_enabled", acrylic_enabled_); loadScalar(j, "acrylic_enabled", acrylic_enabled_);
loadScalar(j, "acrylic_quality", acrylic_quality_); loadScalar(j, "acrylic_quality", acrylic_quality_);
loadScalar(j, "blur_multiplier", blur_multiplier_); loadScalar(j, "blur_multiplier", blur_multiplier_);
@@ -438,6 +448,15 @@ bool Settings::save(const std::string& path)
j["hidden_chat_cids"] = json::array(); j["hidden_chat_cids"] = json::array();
for (const auto& c : hidden_chat_cids_) for (const auto& c : hidden_chat_cids_)
j["hidden_chat_cids"].push_back(c); j["hidden_chat_cids"].push_back(c);
j["chat_emoji_color"] = chat_emoji_color_;
j["chat_poll_rate_sec"] = chat_poll_rate_sec_;
j["chat_bubble_style"] = chat_bubble_style_;
j["chat_bubble_accent"] = chat_bubble_accent_;
j["chat_density"] = chat_density_;
j["chat_font_scale"] = chat_font_scale_;
j["chat_time_format"] = chat_time_format_;
j["chat_enter_sends"] = chat_enter_sends_;
j["time_format"] = time_format_;
j["acrylic_enabled"] = acrylic_enabled_; j["acrylic_enabled"] = acrylic_enabled_;
j["acrylic_quality"] = acrylic_quality_; j["acrylic_quality"] = acrylic_quality_;
j["blur_multiplier"] = blur_multiplier_; j["blur_multiplier"] = blur_multiplier_;

View File

@@ -143,6 +143,27 @@ public:
hidden_chat_cids_.end()); hidden_chat_cids_.end());
} }
// ── Chat-tab customization (chat settings modal + Settings → Chat & Contacts) ──────
bool getChatEmojiColor() const { return chat_emoji_color_; }
void setChatEmojiColor(bool v) { chat_emoji_color_ = v; }
float getChatPollRateSec() const { return chat_poll_rate_sec_; }
void setChatPollRateSec(float v) { chat_poll_rate_sec_ = std::max(0.5f, std::min(15.0f, v)); }
int getChatBubbleStyle() const { return chat_bubble_style_; }
void setChatBubbleStyle(int v) { chat_bubble_style_ = (v < 0 || v > 2) ? 0 : v; }
int getChatBubbleAccent() const { return chat_bubble_accent_; }
void setChatBubbleAccent(int v) { chat_bubble_accent_ = (v < 0 || v > 5) ? 0 : v; }
int getChatDensity() const { return chat_density_; }
void setChatDensity(int v) { chat_density_ = (v < 0 || v > 1) ? 0 : v; }
float getChatFontScale() const { return chat_font_scale_; }
void setChatFontScale(float v) { chat_font_scale_ = std::max(0.8f, std::min(1.5f, v)); }
int getChatTimeFormat() const { return chat_time_format_; } // 0=follow global, 1=24h, 2=12h
void setChatTimeFormat(int v) { chat_time_format_ = (v < 0 || v > 2) ? 0 : v; }
bool getChatEnterSends() const { return chat_enter_sends_; }
void setChatEnterSends(bool v) { chat_enter_sends_ = v; }
// Global clock format (0=24h, 1=12h) — chat can override it for the Chat tab only.
int getTimeFormat() const { return time_format_; }
void setTimeFormat(int v) { time_format_ = (v < 0 || v > 1) ? 0 : v; }
// Privacy // Privacy
bool getSaveZtxs() const { return save_ztxs_; } bool getSaveZtxs() const { return save_ztxs_; }
void setSaveZtxs(bool save) { save_ztxs_ = save; } void setSaveZtxs(bool save) { save_ztxs_ = save; }
@@ -500,6 +521,16 @@ private:
std::string chat_reply_zaddr_; std::string chat_reply_zaddr_;
std::vector<std::string> muted_chat_cids_; // muted chat conversations by cid (Q10) std::vector<std::string> muted_chat_cids_; // muted chat conversations by cid (Q10)
std::vector<std::string> hidden_chat_cids_; // hidden chat conversations by cid std::vector<std::string> hidden_chat_cids_; // hidden chat conversations by cid
// Chat-tab customization (chat settings modal + Settings → Chat & Contacts).
bool chat_emoji_color_ = true; // true = color (needs FreeType; falls back to mono if absent), false = monochrome
float chat_poll_rate_sec_ = 2.5f; // 0-conf chat fast-scan cadence (full node)
int chat_bubble_style_ = 0; // 0 = rounded, 1 = square, 2 = minimal
int chat_bubble_accent_ = 0; // outgoing-bubble accent preset (0 = theme primary)
int chat_density_ = 0; // 0 = comfortable, 1 = compact
float chat_font_scale_ = 1.0f; // message text scale
int chat_time_format_ = 0; // 0 = follow global, 1 = 24h, 2 = 12h (Chat tab only)
bool chat_enter_sends_ = true; // Enter sends (vs. inserts newline; Ctrl+Enter sends)
int time_format_ = 0; // global clock: 0 = 24h, 1 = 12h
bool save_ztxs_ = true; bool save_ztxs_ = true;
bool auto_shield_ = true; bool auto_shield_ = true;
bool use_tor_ = false; bool use_tor_ = false;

View File

@@ -3,6 +3,7 @@
// Released under the GPLv3 // Released under the GPLv3
#include "wallet_state.h" #include "wallet_state.h"
#include "../util/text_format.h" // util::formatClockDateTime (app-wide 24h/12h clock)
#include <algorithm> #include <algorithm>
#include <ctime> #include <ctime>
#include <sstream> #include <sstream>
@@ -44,13 +45,7 @@ int bestSpendableAddressIndex(const std::vector<AddressInfo>& addresses)
std::string TransactionInfo::getTimeString() const std::string TransactionInfo::getTimeString() const
{ {
if (timestamp == 0) return "Unknown"; if (timestamp == 0) return "Unknown";
return util::formatClockDateTime(timestamp);
std::time_t t = static_cast<std::time_t>(timestamp);
std::tm* tm = std::localtime(&t);
std::stringstream ss;
ss << std::put_time(tm, "%Y-%m-%d %H:%M");
return ss.str();
} }
std::string TransactionInfo::getTypeDisplay() const std::string TransactionInfo::getTypeDisplay() const
@@ -77,13 +72,7 @@ std::string PeerInfo::getConnectionTime() const
std::string BannedPeer::getBannedUntilString() const std::string BannedPeer::getBannedUntilString() const
{ {
if (banned_until == 0) return "Never"; if (banned_until == 0) return "Never";
return util::formatClockDateTime(banned_until);
std::time_t t = static_cast<std::time_t>(banned_until);
std::tm* tm = std::localtime(&t);
std::stringstream ss;
ss << std::put_time(tm, "%Y-%m-%d %H:%M");
return ss.str();
} }
} // namespace dragonx } // namespace dragonx

View File

@@ -16,3 +16,4 @@ INCBIN(material_icons, "@CMAKE_SOURCE_DIR@/res/fonts/MaterialIcons-Regular.ttf"
INCBIN(mdi_pickaxe_subset, "@CMAKE_SOURCE_DIR@/res/fonts/MaterialDesignIcons-Pickaxe-Subset.ttf"); INCBIN(mdi_pickaxe_subset, "@CMAKE_SOURCE_DIR@/res/fonts/MaterialDesignIcons-Pickaxe-Subset.ttf");
INCBIN(noto_cjk_subset, "@CMAKE_SOURCE_DIR@/res/fonts/NotoSansCJK-Subset.ttf"); INCBIN(noto_cjk_subset, "@CMAKE_SOURCE_DIR@/res/fonts/NotoSansCJK-Subset.ttf");
INCBIN(noto_emoji_subset, "@CMAKE_SOURCE_DIR@/res/fonts/NotoEmoji-Subset.ttf"); INCBIN(noto_emoji_subset, "@CMAKE_SOURCE_DIR@/res/fonts/NotoEmoji-Subset.ttf");
INCBIN(twemoji_color, "@CMAKE_SOURCE_DIR@/res/fonts/TwemojiMozilla-Color.ttf");

View File

@@ -38,4 +38,8 @@ extern "C" {
extern const unsigned char g_noto_emoji_subset_data[]; extern const unsigned char g_noto_emoji_subset_data[];
extern const unsigned int g_noto_emoji_subset_size; extern const unsigned int g_noto_emoji_subset_size;
// Twemoji COLR/CPAL color-emoji font (used only when color emoji is enabled + FreeType is available).
extern const unsigned char g_twemoji_color_data[];
extern const unsigned int g_twemoji_color_size;
} }

View File

@@ -15,6 +15,11 @@
#include "../embedded/IconsMaterialDesign.h" // Icon codepoint defines #include "../embedded/IconsMaterialDesign.h" // Icon codepoint defines
#include "../../util/logger.h" #include "../../util/logger.h"
#ifdef DRAGONX_HAVE_FREETYPE
#include "imgui_internal.h" // ImFontAtlasGetFontLoaderForStbTruetype
#include "misc/freetype/imgui_freetype.h" // ImGuiFreeType::GetFontLoader + LoadColor flag
#endif
namespace dragonx { namespace dragonx {
namespace ui { namespace ui {
namespace material { namespace material {
@@ -125,7 +130,16 @@ bool Typography::load(ImGuiIO& io, float dpiScale)
float scale = dpiScale * Layout::kFontScale() * Layout::userFontScale(); float scale = dpiScale * Layout::kFontScale() * Layout::userFontScale();
DEBUG_LOGF("Typography: Loading Material Design type scale (DPI: %.2f, fontScale: %.2f, userFontScale: %.2f, combined: %.2f)\n", DEBUG_LOGF("Typography: Loading Material Design type scale (DPI: %.2f, fontScale: %.2f, userFontScale: %.2f, combined: %.2f)\n",
dpiScale, Layout::kFontScale(), Layout::userFontScale(), scale); dpiScale, Layout::kFontScale(), Layout::userFontScale(), scale);
#ifdef DRAGONX_HAVE_FREETYPE
// Choose the atlas font loader BEFORE any font is added: FreeType (required to rasterize COLR
// color-emoji glyphs) when color emoji is enabled, else the default stb_truetype loader. Toggling
// the setting + reload() flips this cleanly.
io.Fonts->SetFontLoader(color_emoji_ ? ImGuiFreeType::GetFontLoader()
: ImFontAtlasGetFontLoaderForStbTruetype());
DEBUG_LOGF("Typography: font loader = %s\n", color_emoji_ ? "FreeType (color emoji)" : "stb_truetype");
#endif
// For ImGui, we need to load fonts at specific pixel sizes. // For ImGui, we need to load fonts at specific pixel sizes.
// Font sizes come from Layout:: accessors (backed by UISchema JSON) // Font sizes come from Layout:: accessors (backed by UISchema JSON)
@@ -335,9 +349,22 @@ ImFont* Typography::loadFont(ImGuiIO& io, int weight, float size, const char* na
}; };
bool wantEmoji = false; bool wantEmoji = false;
for (const char* n : kEmojiFonts) if (strcmp(name, n) == 0) { wantEmoji = true; break; } for (const char* n : kEmojiFonts) if (strcmp(name, n) == 0) { wantEmoji = true; break; }
if (wantEmoji && g_noto_emoji_subset_size > 0) {
void* emojiCopy = IM_ALLOC(g_noto_emoji_subset_size); // Emoji blob: the COLR/CPAL color font (FreeType-rendered) when color emoji is enabled and this
memcpy(emojiCopy, g_noto_emoji_subset_data, g_noto_emoji_subset_size); // is a FreeType build, else the monochrome subset (default / non-FreeType path).
const unsigned char* emojiData = g_noto_emoji_subset_data;
unsigned int emojiSize = g_noto_emoji_subset_size;
bool colorGlyphs = false;
#ifdef DRAGONX_HAVE_FREETYPE
if (color_emoji_ && g_twemoji_color_size > 0) {
emojiData = g_twemoji_color_data;
emojiSize = g_twemoji_color_size;
colorGlyphs = true;
}
#endif
if (wantEmoji && emojiSize > 0) {
void* emojiCopy = IM_ALLOC(emojiSize);
memcpy(emojiCopy, emojiData, emojiSize);
ImFontConfig emojiCfg; ImFontConfig emojiCfg;
emojiCfg.FontDataOwnedByAtlas = true; emojiCfg.FontDataOwnedByAtlas = true;
@@ -346,6 +373,9 @@ ImFont* Typography::loadFont(ImGuiIO& io, int weight, float size, const char* na
emojiCfg.OversampleV = 1; emojiCfg.OversampleV = 1;
emojiCfg.PixelSnapH = true; emojiCfg.PixelSnapH = true;
emojiCfg.GlyphMinAdvanceX = 0; emojiCfg.GlyphMinAdvanceX = 0;
#ifdef DRAGONX_HAVE_FREETYPE
if (colorGlyphs) emojiCfg.FontLoaderFlags |= ImGuiFreeTypeLoaderFlags_LoadColor; // render COLR in color
#endif
// The base Ubuntu font already owns U+260026FF etc.; MergeMode keeps the first-loaded glyph, // The base Ubuntu font already owns U+260026FF etc.; MergeMode keeps the first-loaded glyph,
// so its text-style symbols win and only the codepoints it lacks fall through to emoji. // so its text-style symbols win and only the codepoints it lacks fall through to emoji.
static const ImWchar emojiRanges[] = { static const ImWchar emojiRanges[] = {
@@ -355,11 +385,13 @@ ImFont* Typography::loadFont(ImGuiIO& io, int weight, float size, const char* na
0, 0,
}; };
emojiCfg.GlyphRanges = emojiRanges; emojiCfg.GlyphRanges = emojiRanges;
snprintf(emojiCfg.Name, sizeof(emojiCfg.Name), "NotoEmoji %.0fpx (merge)", size); snprintf(emojiCfg.Name, sizeof(emojiCfg.Name), "%s %.0fpx (merge)",
colorGlyphs ? "Twemoji" : "NotoEmoji", size);
ImFont* emojiMerge = io.Fonts->AddFontFromMemoryTTF(emojiCopy, g_noto_emoji_subset_size, size, &emojiCfg); ImFont* emojiMerge = io.Fonts->AddFontFromMemoryTTF(emojiCopy, emojiSize, size, &emojiCfg);
if (emojiMerge) { if (emojiMerge) {
DEBUG_LOGF("Typography: Merged emoji (%u bytes) into %s OK\n", g_noto_emoji_subset_size, name); DEBUG_LOGF("Typography: Merged %s emoji (%u bytes) into %s OK\n",
colorGlyphs ? "color" : "mono", emojiSize, name);
} else { } else {
DEBUG_LOGF("Typography: WARNING — emoji merge FAILED for %s (size=%u)\n", name, size); DEBUG_LOGF("Typography: WARNING — emoji merge FAILED for %s (size=%u)\n", name, size);
} }

View File

@@ -112,6 +112,14 @@ public:
* @brief Get the current DPI scale * @brief Get the current DPI scale
*/ */
float getDpiScale() const { return dpiScale_; } float getDpiScale() const { return dpiScale_; }
/**
* @brief Select color vs monochrome emoji for the next (re)load. Color needs a FreeType-enabled
* build (DRAGONX_HAVE_FREETYPE); otherwise this is inert and monochrome is always used.
* Set before load()/reload() (App does this from the chat_emoji_color setting).
*/
void setColorEmoji(bool enabled) { color_emoji_ = enabled; }
bool colorEmoji() const { return color_emoji_; }
/** /**
* @brief Get font for a type style * @brief Get font for a type style
@@ -261,7 +269,8 @@ private:
bool loaded_ = false; bool loaded_ = false;
float dpiScale_ = 1.0f; float dpiScale_ = 1.0f;
bool color_emoji_ = false; // when true + FreeType present, merge the COLR color-emoji font
// Fonts for each type style // Fonts for each type style
ImFont* fonts_[15] = {}; ImFont* fonts_[15] = {};

View File

@@ -12,6 +12,7 @@
#include <sodium.h> #include <sodium.h>
#include "../../util/logger.h" #include "../../util/logger.h"
#include "../windows/balance_tab.h" #include "../windows/balance_tab.h"
#include "../windows/chat_tab.h" // RenderChatSettingsControls (shared Chat & Contacts controls)
#include "../windows/console_tab.h" #include "../windows/console_tab.h"
#include "../../util/i18n.h" #include "../../util/i18n.h"
#include "../../util/platform.h" #include "../../util/platform.h"
@@ -821,6 +822,25 @@ void RenderSettingsPage(App* app) {
ImGui::Dummy(ImVec2(0, Layout::spacingSm())); ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
// --- Clock format (app-wide 24h / 12h — the Chat tab can override it in chat settings) ---
{
ImGui::PushFont(body2);
ImGui::AlignTextToFramePadding();
ImGui::TextUnformatted(TR("clock_format"));
ImGui::SameLine(0, Layout::spacingMd());
int cf = app->settings() ? app->settings()->getTimeFormat() : 0;
const char* cfItems[] = { TR("chat_ts_24h"), TR("chat_ts_12h") };
ImGui::SetNextItemWidth(160.0f * Layout::dpiScale());
if (ImGui::Combo("##ClockFormat", &cf, cfItems, 2) && app->settings()) {
app->settings()->setTimeFormat(cf);
app->settings()->save();
}
if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_clock_format"));
ImGui::PopFont();
}
ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
// --- Font Scale slider (always visible) --- // --- Font Scale slider (always visible) ---
{ {
ImGui::PushFont(body2); ImGui::PushFont(body2);
@@ -1086,6 +1106,25 @@ void RenderSettingsPage(App* app) {
ImGui::Dummy(ImVec2(0, Layout::spacingSm())); ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
// --- Clock format (app-wide 24h / 12h — the Chat tab can override it in chat settings) ---
{
ImGui::PushFont(body2);
ImGui::AlignTextToFramePadding();
ImGui::TextUnformatted(TR("clock_format"));
ImGui::SameLine(0, Layout::spacingMd());
int cf = app->settings() ? app->settings()->getTimeFormat() : 0;
const char* cfItems[] = { TR("chat_ts_24h"), TR("chat_ts_12h") };
ImGui::SetNextItemWidth(160.0f * Layout::dpiScale());
if (ImGui::Combo("##ClockFormat", &cf, cfItems, 2) && app->settings()) {
app->settings()->setTimeFormat(cf);
app->settings()->save();
}
if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_clock_format"));
ImGui::PopFont();
}
ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
// --- Font Scale slider (always visible) --- // --- Font Scale slider (always visible) ---
{ {
ImGui::PushFont(body2); ImGui::PushFont(body2);
@@ -2445,6 +2484,21 @@ void RenderSettingsPage(App* app) {
ImGui::Dummy(ImVec2(0, gap)); ImGui::Dummy(ImVec2(0, gap));
// ====================================================================
// CHAT & CONTACTS — card (same controls as the Chat tab's settings notch)
// ====================================================================
{
Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), TR("chat_settings_section"));
ImGui::Dummy(ImVec2(0, Layout::spacingXs()));
material::GlassCardScope card(dl, availWidth, pad, bottomPad, glassSpec);
ImGui::PushFont(body2);
RenderChatSettingsControls(app, availWidth - pad * 2.0f); // card inner width (GlassCard doesn't narrow it)
ImGui::PopFont();
}
ImGui::Dummy(ImVec2(0, gap));
// ==================================================================== // ====================================================================
// ABOUT — card // ABOUT — card
// ==================================================================== // ====================================================================

View File

@@ -203,10 +203,7 @@ void BlockInfoDialog::render(App* app)
ImGui::Text("%s", TR("block_timestamp")); ImGui::Text("%s", TR("block_timestamp"));
ImGui::SameLine(lbl.position); ImGui::SameLine(lbl.position);
if (s_block_time > 0) { if (s_block_time > 0) {
std::time_t t = static_cast<std::time_t>(s_block_time); ImGui::Text("%s", dragonx::util::formatClockDateTime(s_block_time, /*withSeconds=*/true).c_str());
char time_buf[64];
std::strftime(time_buf, sizeof(time_buf), "%Y-%m-%d %H:%M:%S", std::localtime(&t));
ImGui::Text("%s", time_buf);
} else { } else {
ImGui::TextDisabled("%s", TR("unknown")); ImGui::TextDisabled("%s", TR("unknown"));
} }

File diff suppressed because it is too large Load Diff

View File

@@ -24,6 +24,18 @@ namespace ui {
*/ */
void RenderChatTab(App* app); void RenderChatTab(App* app);
/**
* @brief Render the chat-customization controls (emoji style, poll rate, bubble style/color,
* density, text size, chat timestamps, Enter-to-send). Shared by the Chat tab's settings
* "notch" modal and the Settings tab's Chat & Contacts section. The app-wide clock format
* (which chat timestamps fall back to) lives separately in Settings → General.
* @param app Pointer to the app instance.
* @param contentWidth Explicit row width for right-aligning controls; pass the card's inner content
* width from the Settings tab (whose GlassCard doesn't narrow the content region). 0 = auto
* (use the current content region, correct inside the chat modal's dialog).
*/
void RenderChatSettingsControls(App* app, float contentWidth = 0.0f);
/** /**
* @brief Securely wipe the Chat tab's UI-local state (composer / new-conversation * @brief Securely wipe the Chat tab's UI-local state (composer / new-conversation
* plaintext buffers + the selected-conversation ids). Called by * plaintext buffers + the selected-conversation ids). Called by

View File

@@ -1127,10 +1127,8 @@ static void renderBlockDetailModal(App* app) {
// Row 1: Timestamp | Confirmations // Row 1: Timestamp | Confirmations
drawLabelValue(dl, gx, gy, labelW, TR("block_timestamp"), "", capFont, sub1); drawLabelValue(dl, gx, gy, labelW, TR("block_timestamp"), "", capFont, sub1);
if (s_detail_time > 0) { if (s_detail_time > 0) {
std::time_t t = static_cast<std::time_t>(s_detail_time); const std::string tstr = dragonx::util::formatClockDateTime(s_detail_time, /*withSeconds=*/true);
char time_buf[64]; dl->AddText(sub1, sub1->LegacySize, ImVec2(gx + labelW, gy), OnSurface(), tstr.c_str());
std::strftime(time_buf, sizeof(time_buf), "%Y-%m-%d %H:%M:%S", std::localtime(&t));
dl->AddText(sub1, sub1->LegacySize, ImVec2(gx + labelW, gy), OnSurface(), time_buf);
} }
dl->AddText(capFont, capFont->LegacySize, ImVec2(gx + halfW, gy), OnSurfaceMedium(), TR("confirmations")); dl->AddText(capFont, capFont->LegacySize, ImVec2(gx + halfW, gy), OnSurfaceMedium(), TR("confirmations"));
snprintf(buf, sizeof(buf), "%d", s_detail_confirmations); snprintf(buf, sizeof(buf), "%d", s_detail_confirmations);

View File

@@ -64,10 +64,7 @@ struct DisplayTx {
std::string DisplayTx::getTimeString() const { std::string DisplayTx::getTimeString() const {
if (timestamp <= 0) return TR("pending"); if (timestamp <= 0) return TR("pending");
std::time_t t = static_cast<std::time_t>(timestamp); return dragonx::util::formatClockDateTime(timestamp); // honors the app-wide 24h/12h clock
char buf[64];
std::strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M", std::localtime(&t));
return buf;
} }
// Relative time string (localized long form, e.g. "5 minutes ago") // Relative time string (localized long form, e.g. "5 minutes ago")

View File

@@ -217,11 +217,11 @@ void I18n::loadBuiltinEnglish()
strings_["chat_you"] = "You"; strings_["chat_you"] = "You";
strings_["chat_contact_request"] = "contact request"; strings_["chat_contact_request"] = "contact request";
strings_["chat_send_failed"] = "not sent"; strings_["chat_send_failed"] = "not sent";
strings_["chat_new_button"] = "New conversation"; strings_["chat_new_button"] = "New chat";
strings_["chat_select_hint"] = "Select a conversation to view it."; strings_["chat_select_hint"] = "Select a conversation to view it.";
strings_["chat_waiting_reply"] = "Waiting for this contact to reply — you can message them once they do."; strings_["chat_waiting_reply"] = "Waiting for this contact to reply — you can message them once they do.";
strings_["chat_send"] = "Send"; strings_["chat_send"] = "Send";
strings_["chat_new_title"] = "New conversation"; strings_["chat_new_title"] = "New chat";
strings_["chat_new_zaddr"] = "Recipient z-address"; strings_["chat_new_zaddr"] = "Recipient z-address";
strings_["chat_new_message"] = "Message"; strings_["chat_new_message"] = "Message";
strings_["chat_new_send"] = "Send request"; strings_["chat_new_send"] = "Send request";
@@ -260,6 +260,47 @@ void I18n::loadBuiltinEnglish()
strings_["chat_hidden_toast"] = "Conversation hidden — a new message brings it back"; strings_["chat_hidden_toast"] = "Conversation hidden — a new message brings it back";
strings_["chat_pick_contact"] = "Choose from contacts\xE2\x80\xA6"; strings_["chat_pick_contact"] = "Choose from contacts\xE2\x80\xA6";
strings_["chat_no_z_contacts"] = "No shielded-address contacts yet"; strings_["chat_no_z_contacts"] = "No shielded-address contacts yet";
strings_["chat_copy_address_tip"] = "Click to copy address";
strings_["chat_verify_key"] = "Identity key \xE2\x80\x94 compare to verify";
strings_["chat_awaiting_key"] = "Waiting for reply";
strings_["chat_rename"] = "Rename contact";
strings_["chat_rename_hint"] = "Contact name";
strings_["chat_renamed"] = "Contact renamed";
// Chat customization (gear modal + Settings → Chat & Contacts)
strings_["chat_settings_title"] = "Chat settings";
strings_["chat_settings_tip"] = "Chat customization";
strings_["chat_settings_section"] = "CHAT & CONTACTS";
strings_["chat_opt_emoji"] = "Emoji style";
strings_["chat_emoji_mono"] = "Monochrome";
strings_["chat_emoji_color"] = "Color";
strings_["chat_opt_poll"] = "Message poll rate";
strings_["chat_opt_bubble_style"] = "Bubble style";
strings_["chat_bubble_rounded"] = "Rounded";
strings_["chat_bubble_square"] = "Square";
strings_["chat_bubble_minimal"] = "Minimal";
strings_["chat_opt_bubble_accent"] = "Bubble color";
strings_["chat_accent_theme"] = "Theme";
strings_["chat_accent_blue"] = "Blue";
strings_["chat_accent_green"] = "Green";
strings_["chat_accent_purple"] = "Purple";
strings_["chat_accent_amber"] = "Amber";
strings_["chat_accent_pink"] = "Pink";
strings_["chat_opt_density"] = "Message density";
strings_["chat_density_comfortable"] = "Comfortable";
strings_["chat_density_compact"] = "Compact";
strings_["chat_opt_font_size"] = "Text size";
strings_["chat_opt_timestamp"] = "Timestamps";
strings_["chat_ts_global"] = "Follow global";
strings_["chat_ts_24h"] = "24-hour";
strings_["chat_ts_12h"] = "12-hour";
strings_["chat_opt_enter_sends"] = "Enter sends message";
strings_["chat_opt_global_clock"] = "Global clock format";
strings_["chat_settings_done"] = "Done";
strings_["chat_ts_global_short"] = "Global";
strings_["chat_sec_appearance"] = "APPEARANCE";
strings_["chat_sec_messaging"] = "MESSAGING";
strings_["chat_today"] = "Today";
strings_["chat_yesterday"] = "Yesterday";
// Seed-phrase backup (full-node) // Seed-phrase backup (full-node)
strings_["seed_backup_button"] = "Seed phrase"; strings_["seed_backup_button"] = "Seed phrase";
strings_["tt_seed_backup"] = "Show and back up your wallet's 24-word recovery seed phrase"; strings_["tt_seed_backup"] = "Show and back up your wallet's 24-word recovery seed phrase";
@@ -1145,6 +1186,8 @@ void I18n::loadBuiltinEnglish()
strings_["network"] = "Network"; strings_["network"] = "Network";
strings_["theme"] = "Theme"; strings_["theme"] = "Theme";
strings_["language"] = "Language"; strings_["language"] = "Language";
strings_["clock_format"] = "Clock format";
strings_["tt_clock_format"] = "24-hour or 12-hour clock, used across the app. The Chat tab can override it in its own settings.";
strings_["dragonx_green"] = "DragonX (Green)"; strings_["dragonx_green"] = "DragonX (Green)";
strings_["dark"] = "Dark"; strings_["dark"] = "Dark";
strings_["light"] = "Light"; strings_["light"] = "Light";

View File

@@ -19,8 +19,38 @@ std::int64_t secondsAgo(std::int64_t timestamp)
std::int64_t diff = now - timestamp; std::int64_t diff = now - timestamp;
return diff < 0 ? 0 : diff; return diff < 0 ? 0 : diff;
} }
bool g_clock12h = false; // app-wide 12-hour clock preference (synced from settings each frame)
} // namespace } // namespace
void setClock12h(bool enabled) { g_clock12h = enabled; }
bool clock12h() { return g_clock12h; }
std::string formatClockDateTime(std::int64_t timestamp, bool withSeconds)
{
if (timestamp <= 0) return {};
std::time_t t = static_cast<std::time_t>(timestamp);
std::tm* tm = std::localtime(&t); // UI thread only
if (!tm) return {};
const char* fmt = g_clock12h
? (withSeconds ? "%Y-%m-%d %I:%M:%S %p" : "%Y-%m-%d %I:%M %p")
: (withSeconds ? "%Y-%m-%d %H:%M:%S" : "%Y-%m-%d %H:%M");
char buf[40];
return std::strftime(buf, sizeof(buf), fmt, tm) > 0 ? std::string(buf) : std::string();
}
std::string formatClockTime(std::int64_t timestamp, bool withSeconds)
{
if (timestamp <= 0) return {};
std::time_t t = static_cast<std::time_t>(timestamp);
std::tm* tm = std::localtime(&t); // UI thread only
if (!tm) return {};
const char* fmt = g_clock12h
? (withSeconds ? "%I:%M:%S %p" : "%I:%M %p")
: (withSeconds ? "%H:%M:%S" : "%H:%M");
char buf[24];
return std::strftime(buf, sizeof(buf), fmt, tm) > 0 ? std::string(buf) : std::string();
}
std::string formatTimeAgo(std::int64_t timestamp) std::string formatTimeAgo(std::int64_t timestamp)
{ {
if (timestamp <= 0) return {}; if (timestamp <= 0) return {};

View File

@@ -14,6 +14,19 @@
namespace dragonx { namespace dragonx {
namespace util { namespace util {
// App-wide clock format (24-hour vs 12-hour). Set once per frame by the App from the global
// `time_format` setting (setClock12h); the formatters below read it so a single preference drives
// every user-facing timestamp. The Chat tab resolves its own override separately.
void setClock12h(bool enabled);
bool clock12h();
// Format an epoch timestamp as a local wall-clock date+time honoring the app clock setting:
// 24h -> "YYYY-MM-DD HH:MM[:SS]", 12h -> "YYYY-MM-DD hh:MM[:SS] AM/PM". Empty when ts <= 0.
std::string formatClockDateTime(std::int64_t timestamp, bool withSeconds = false);
// Just the time-of-day portion honoring the clock setting: 24h "HH:MM[:SS]" / 12h "hh:MM[:SS] AM/PM".
std::string formatClockTime(std::int64_t timestamp, bool withSeconds = false);
// Localized relative time, e.g. "5 minutes ago" (via i18n). Empty when timestamp <= 0. // Localized relative time, e.g. "5 minutes ago" (via i18n). Empty when timestamp <= 0.
std::string formatTimeAgo(std::int64_t timestamp); std::string formatTimeAgo(std::int64_t timestamp);