diff --git a/.cursor/api_usage.md b/.cursor/api_usage.md new file mode 100644 index 0000000000..c551f0b7ce --- /dev/null +++ b/.cursor/api_usage.md @@ -0,0 +1,100 @@ +# Telegram Desktop API Usage + +## API Schema + +The API definitions are described using [TL Language](https://core.telegram.org/mtproto/TL) in two main schema files: + +1. **`Telegram/SourceFiles/mtproto/scheme/mtproto.tl`** + * Defines the core MTProto protocol types and methods used for basic communication, encryption, authorization, service messages, etc. + * Some fundamental types and methods from this schema (like basic types, RPC calls, containers) are often implemented directly in the C++ MTProto core (`SourceFiles/mtproto/`) and may be skipped during the C++ code generation phase. + * Other parts of `mtproto.tl` might still be processed by the code generator. + +2. **`Telegram/SourceFiles/mtproto/scheme/api.tl`** + * Defines the higher-level Telegram API layer, including all the methods and types related to chat functionality, user profiles, messages, channels, stickers, etc. + * This is the primary schema used when making functional API requests within the application. + +Both files use the same TL syntax to describe API methods (functions) and types (constructors). + +## Code Generation + +A custom code generation tool processes `api.tl` (and parts of `mtproto.tl`) to create corresponding C++ classes and types. These generated headers are typically included via the Precompiled Header (PCH) for the main `Telegram` project. + +Generated types often follow the pattern `MTP[Type]` (e.g., `MTPUser`, `MTPMessage`) and methods correspond to functions within the `MTP` namespace or related classes (e.g., `MTPmessages_SendMessage`). + +## Making API Requests + +API requests are made using a standard pattern involving the `api()` object (providing access to the `MTP::Instance`), the generated `MTP...` request object, callback handlers for success (`.done()`) and failure (`.fail()`), and the `.send()` method. + +Here's the general structure: + +```cpp +// Include necessary headers if not already in PCH + +// Obtain the API instance (usually via api() or MTP::Instance::Get()) +api().request(MTPnamespace_MethodName( + // Constructor arguments based on the api.tl definition for the method + MTP_flags(flags_value), // Use MTP_flags if the method has flags + MTP_inputPeer(peer), // Use MTP_... types for parameters + MTP_string(messageText), + MTP_long(randomId), + // ... other arguments matching the TL definition + MTP_vector() // Example for a vector argument +)).done([=](const MTPResponseType &result) { + // Handle the successful response (result). + // 'result' will be of the C++ type corresponding to the TL type + // specified after the '=' in the api.tl method definition. + // How to access data depends on whether the TL type has one or multiple constructors: + + // 1. Multiple Constructors (e.g., User = User | UserEmpty): + // Use .match() with lambdas for each constructor: + result.match([&](const MTPDuser &data) { + /* use data.vfirst_name().v, etc. */ + }, [&](const MTPDuserEmpty &data) { + /* handle empty user */ + }); + + // Alternatively, check the type explicitly and use the constructor getter: + if (result.type() == mtpc_user) { + const auto &data = result.c_user(); // Asserts if type is not mtpc_user! + // use data.vfirst_name().v + } else if (result.type() == mtpc_userEmpty) { + const auto &data = result.c_userEmpty(); + // handle empty user + } + + // 2. Single Constructor (e.g., Messages = messages { msgs: vector }): + // Use .match() with a single lambda: + result.match([&](const MTPDmessages &data) { /* use data.vmessages().v */ }); + + // Or check the type explicitly and use the constructor getter: + if (result.type() == mtpc_messages) { + const auto &data = result.c_messages(); // Asserts if type is not mtpc_messages! + // use data.vmessages().v + } + + // Or use the shortcut .data() for single-constructor types: + const auto &data = result.data(); // Only works for single-constructor types! + // use data.vmessages().v + +}).fail([=](const MTP::Error &error) { + // Handle the API error (error). + // 'error' is an MTP::Error object containing the error code (error.type()) + // and description (error.description()). Check for specific error strings. + if (error.type() == u"FLOOD_WAIT_X"_q) { + // Handle flood wait + } else { + Ui::show(Box(Lang::Hard::ServerError())); // Example generic error handling + } +}).handleFloodErrors().send(); // handleFloodErrors() is common, then send() +``` + +**Key Points:** + +* Always refer to `Telegram/SourceFiles/mtproto/scheme/api.tl` for the correct method names, parameters (names and types), and response types. +* Use the generated `MTP...` types/classes for request parameters (e.g., `MTP_int`, `MTP_string`, `MTP_bool`, `MTP_vector`, `MTPInputUser`, etc.) and response handling. +* The `.done()` lambda receives the specific C++ `MTP...` type corresponding to the TL return type. + * For types with **multiple constructors** (e.g., `User = User | UserEmpty`), use `result.match([&](const MTPDuser &d){ ... }, [&](const MTPDuserEmpty &d){ ... })` to handle each case, or check `result.type() == mtpc_user` / `mtpc_userEmpty` and call the specific `result.c_user()` / `result.c_userEmpty()` getter (which asserts on type mismatch). + * For types with a **single constructor** (e.g., `Messages = messages{...}`), you can use `result.match([&](const MTPDmessages &d){ ... })` with one lambda, or check `type()` and call `c_messages()`, or use the shortcut `result.data()` to access the fields directly. +* The `.fail()` lambda receives an `MTP::Error` object. Check `error.type()` against known error strings (often defined as constants or using `u"..."_q` literals). +* Directly construct the `MTPnamespace_MethodName(...)` object inside `request()`. +* Include `.handleFloodErrors()` before `.send()` for standard flood wait handling. \ No newline at end of file diff --git a/.cursor/localization.md b/.cursor/localization.md new file mode 100644 index 0000000000..cf39e7d6fd --- /dev/null +++ b/.cursor/localization.md @@ -0,0 +1,159 @@ +# Telegram Desktop Localization + +## Coding Style Note + +**Use `auto`:** In the actual codebase, variable types are almost always deduced using `auto` (or `const auto`, `const auto &`) rather than being written out explicitly. Examples in this guide may use explicit types for clarity, but prefer `auto` in practice. + +```cpp +// Prefer this: +auto currentTitle = tr::lng_settings_title(tr::now); +auto nameProducer = GetNameProducer(); // Returns rpl::producer<...> + +// Instead of this: +QString currentTitle = tr::lng_settings_title(tr::now); +rpl::producer nameProducer = GetNameProducer(); +``` + +## String Resource File + +Base user-facing English strings are defined in the `lang.strings` file: + +`Telegram/Resources/langs/lang.strings` + +This file uses a key-value format with named placeholders: + +``` +"lng_settings_title" = "Settings"; +"lng_confirm_delete_item" = "Are you sure you want to delete {item_name}?"; +"lng_files_selected" = "{count} files selected"; // Simple count example (see Pluralization) +``` + +Placeholders are enclosed in curly braces, e.g., `{name}`, `{user}`. A special placeholder `{count}` is used for pluralization rules. + +### Pluralization + +For keys that depend on a number (using the `{count}` placeholder), English typically requires two forms: singular and plural. These are defined in `lang.strings` using `#one` and `#other` suffixes: + +``` +"lng_files_selected#one" = "{count} file selected"; +"lng_files_selected#other" = "{count} files selected"; +``` + +While only `#one` and `#other` are defined in the base `lang.strings`, the code generation process creates C++ accessors for all six CLDR plural categories (`#zero`, `#one`, `#two`, `#few`, `#many`, `#other`) to support languages with more complex pluralization rules. + +## Translation Process + +While `lang.strings` provides the base English text and the keys, the actual translations are managed via Telegram's translations platform (translations.telegram.org) and loaded dynamically at runtime from the API. The keys from `lang.strings` (including the `#one`/`#other` variants) are used on the platform. + +## Code Generation + +A code generation tool processes `lang.strings` to create C++ structures and accessors within the `tr` namespace. These allow type-safe access to strings and handling of placeholders and pluralization. Generated keys typically follow the pattern `tr::lng_key_name`. + +## String Usage in Code + +Strings are accessed in C++ code using the generated objects within the `tr::` namespace. There are two main ways to use them: reactively (returning an `rpl::producer`) or immediately (returning the current value). + +### 1. Reactive Usage (rpl::producer) + +Calling a generated string function directly returns a reactive producer, typically `rpl::producer`. This producer automatically updates its value whenever the application language changes. + +```cpp +// Key: "settings_title" = "Settings"; +auto titleProducer = tr::lng_settings_title(); // Type: rpl::producer + +// Key: "confirm_delete_item" = "Are you sure you want to delete {item_name}?"; +auto itemNameProducer = /* ... */; // Type: rpl::producer +auto confirmationProducer = tr::lng_confirm_delete_item( // Type: rpl::producer + tr::now, // NOTE: tr::now is NOT passed here for reactive result + lt_item_name, + std::move(itemNameProducer)); // Placeholder producers should be moved +``` + +### 2. Immediate Usage (Current Value) + +Passing `tr::now` as the first argument retrieves the string's current value in the active language (typically as a `QString`). + +```cpp +// Key: "settings_title" = "Settings"; +auto currentTitle = tr::lng_settings_title(tr::now); // Type: QString + +// Key: "confirm_delete_item" = "Are you sure you want to delete {item_name}?"; +const auto currentItemName = QString("My Document"); // Type: QString +auto currentConfirmation = tr::lng_confirm_delete_item( // Type: QString + tr::now, // Pass tr::now for immediate value + lt_item_name, currentItemName); // Placeholder value is a direct QString (or convertible) +``` + +### 3. Placeholders (`{tag}`) + +Placeholders like `{item_name}` are replaced by providing arguments after `tr::now` (for immediate) or as the initial arguments (for reactive). A corresponding `lt_tag_name` constant is passed before the value. + +* **Immediate:** Pass the direct value (e.g., `QString`, `int`). +* **Reactive:** Pass an `rpl::producer` of the corresponding type (e.g., `rpl::producer`). Remember to `std::move` the producer or use `rpl::duplicate` if you need to reuse the original producer afterwards. + +### 4. Pluralization (`{count}`) + +Keys using `{count}` require a numeric value for the `lt_count` placeholder. The correct plural form (`#zero`, `#one`, ..., `#other`) is automatically selected based on this value and the current language rules. + +* **Immediate (`tr::now`):** Pass a `float64` or `int` (which is auto-converted to `float64`). + ```cpp + int count = 1; + auto filesText = tr::lng_files_selected(tr::now, lt_count, count); // Type: QString + count = 5; + filesText = tr::lng_files_selected(tr::now, lt_count, count); // Uses "files_selected#other" + ``` + +* **Reactive:** Pass an `rpl::producer`. Use the `tr::to_count()` helper to convert an `rpl::producer` or wrap a single value. + ```cpp + // From an existing int producer: + auto countProducer = /* ... */; // Type: rpl::producer + auto filesTextProducer = tr::lng_files_selected( // Type: rpl::producer + lt_count, + countProducer | tr::to_count()); // Use tr::to_count() for conversion + + // From a single int value wrapped reactively: + int currentCount = 5; + auto filesTextProducerSingle = tr::lng_files_selected( // Type: rpl::producer + lt_count, + rpl::single(currentCount) | tr::to_count()); + // Alternative for single values (less common): rpl::single(currentCount * 1.) + ``` + +### 5. Custom Projectors + +An optional final argument can be a projector function (like `Ui::Text::Upper` or `Ui::Text::WithEntities`) to transform the output. + +* If the projector returns `OutputType`, the string function returns `OutputType` (immediate) or `rpl::producer` (reactive). +* Placeholder values must match the projector's *input* requirements. For `Ui::Text::WithEntities`, placeholders expect `TextWithEntities` (immediate) or `rpl::producer` (reactive). + +```cpp +// Immediate with Ui::Text::WithEntities projector +// Key: "user_posted_photo" = "{user} posted a photo"; +const auto userName = TextWithEntities{ /* ... */ }; // Type: TextWithEntities +auto message = tr::lng_user_posted_photo( // Type: TextWithEntities + tr::now, + lt_user, + userName, // Must be TextWithEntities + Ui::Text::WithEntities); // Projector + +// Reactive with Ui::Text::WithEntities projector +auto userNameProducer = /* ... */; // Type: rpl::producer +auto messageProducer = tr::lng_user_posted_photo( // Type: rpl::producer + lt_user, + std::move(userNameProducer), // Move placeholder producers + Ui::Text::WithEntities); // Projector +``` + +## Key Summary + +* Keys are defined in `Resources/langs/lang.strings` using `{tag}` placeholders. +* Plural keys use `{count}` and have `#one`/`#other` variants in `lang.strings`. +* Access keys via `tr::lng_key_name(...)` in C++. +* Call with `tr::now` as the first argument for the immediate `QString` (or projected type). +* Call without `tr::now` for the reactive `rpl::producer` (or projected type). +* Provide placeholder values (`lt_tag_name, value`) matching the usage (direct value for immediate, `rpl::producer` for reactive). Producers should typically be moved via `std::move`. +* For `{count}`: + * Immediate: Pass `int` or `float64`. + * Reactive: Pass `rpl::producer`, typically by converting an `int` producer using `| tr::to_count()`. +* Optional projector function as the last argument modifies the output type and required placeholder types. +* Actual translations are loaded at runtime from the API. \ No newline at end of file diff --git a/.cursor/rpl_guide.md b/.cursor/rpl_guide.md new file mode 100644 index 0000000000..8ff7be46a5 --- /dev/null +++ b/.cursor/rpl_guide.md @@ -0,0 +1,211 @@ +# RPL (Reactive Programming Library) Guide + +## Coding Style Note + +**Use `auto`:** In the actual codebase, variable types are almost always deduced using `auto` (or `const auto`, `const auto &`) rather than being written out explicitly. Examples in this guide may use explicit types for clarity, but prefer `auto` in practice. + +```cpp +// Prefer this: +auto intProducer = rpl::single(123); +const auto &lifetime = existingLifetime; + +// Instead of this: +rpl::producer intProducer = rpl::single(123); +const rpl::lifetime &lifetime = existingLifetime; + +// Sometimes needed if deduction is ambiguous or needs help: +auto user = std::make_shared(); +auto data = QByteArray::fromHex("..."); +``` + +## Introduction + +RPL is the reactive programming library used in this project, residing in the `rpl::` namespace. It allows handling asynchronous streams of data over time. + +The core concept is the `rpl::producer`, which represents a stream of values that can be generated over a certain lifetime. + +## Producers: `rpl::producer` + +The fundamental building block is `rpl::producer`. It produces values of `Type` and can optionally signal an error of type `Error`. By default, `Error` is `rpl::no_error`, indicating that the producer does not explicitly handle error signaling through this mechanism. + +```cpp +// A producer that emits integers. +auto intProducer = /* ... */; // Type: rpl::producer + +// A producer that emits strings and can potentially emit a CustomError. +auto stringProducerWithError = /* ... */; // Type: rpl::producer +``` + +Producers are typically lazy; they don't start emitting values until someone subscribes to them. + +## Lifetime Management: `rpl::lifetime` + +Reactive pipelines have a limited duration, managed by `rpl::lifetime`. An `rpl::lifetime` object essentially holds a collection of cleanup callbacks. When the lifetime ends (either explicitly destroyed or goes out of scope), these callbacks are executed, tearing down the associated pipeline and freeing resources. + +```cpp +rpl::lifetime myLifetime; +// ... later ... +// myLifetime is destroyed, cleanup happens. + +// Or, pass a lifetime instance to manage a pipeline's duration. +rpl::lifetime &parentLifetime = /* ... get lifetime from context ... */; +``` + +## Starting a Pipeline: `rpl::start_...` + +To consume values from a producer, you start a pipeline using one of the `rpl::start_...` methods. These methods subscribe to the producer and execute callbacks for the events they handle. + +The most common method is `rpl::start_with_next`: + +```cpp +auto counter = /* ... */; // Type: rpl::producer +rpl::lifetime lifetime; + +// Counter is consumed here, use std::move if it's an l-value. +std::move( + counter +) | rpl::start_with_next([=](int nextValue) { + // Process the next integer value emitted by the producer. + qDebug() << "Received: " << nextValue; +}, lifetime); // Pass the lifetime to manage the subscription. +// Note: `counter` is now in a moved-from state and likely invalid. + +// If you need to start the same producer multiple times, duplicate it: +// rpl::duplicate(counter) | rpl::start_with_next(...); + +// If you DON'T pass a lifetime to a start_... method: +auto counter2 = /* ... */; // Type: rpl::producer +rpl::lifetime subscriptionLifetime = std::move( + counter2 +) | rpl::start_with_next([=](int nextValue) { /* ... */ }); +// The returned lifetime MUST be stored. If it's discarded immediately, +// the subscription stops instantly. +// `counter2` is also moved-from here. +``` + +Other variants allow handling errors (`_error`) and completion (`_done`): + +```cpp +auto dataStream = /* ... */; // Type: rpl::producer +rpl::lifetime lifetime; + +// Assuming dataStream might be used again, we duplicate it for the first start. +// If it's the only use, std::move(dataStream) would be preferred. +rpl::duplicate( + dataStream +) | rpl::start_with_error([=](Error &&error) { + // Handle the error signaled by the producer. + qDebug() << "Error: " << error.text(); +}, lifetime); + +// Using dataStream again, perhaps duplicated again or moved if last use. +rpl::duplicate( + dataStream +) | rpl::start_with_done([=]() { + // Execute when the producer signals it's finished emitting values. + qDebug() << "Stream finished."; +}, lifetime); + +// Last use of dataStream, so we move it. +std::move( + dataStream +) | rpl::start_with_next_error_done( + [=](QString &&value) { /* handle next value */ }, + [=](Error &&error) { /* handle error */ }, + [=]() { /* handle done */ }, + lifetime); +``` + +## Transforming Producers + +RPL provides functions to create new producers by transforming existing ones: + +* `rpl::map`: Transforms each value emitted by a producer. + ```cpp + auto ints = /* ... */; // Type: rpl::producer + // The pipe operator often handles the move implicitly for chained transformations. + auto strings = std::move( + ints // Explicit move is safer + ) | rpl::map([](int value) { + return QString::number(value * 2); + }); // Emits strings like "0", "2", "4", ... + ``` + +* `rpl::filter`: Emits only the values from a producer that satisfy a condition. + ```cpp + auto ints = /* ... */; // Type: rpl::producer + auto evenInts = std::move( + ints // Explicit move is safer + ) | rpl::filter([](int value) { + return (value % 2 == 0); + }); // Emits only even numbers. + ``` + +## Combining Producers + +You can combine multiple producers into one: + +* `rpl::combine`: Combines the latest values from multiple producers whenever *any* of them emits a new value. Requires all producers to have emitted at least one value initially. + While it produces a `std::tuple`, subsequent operators like `map`, `filter`, and `start_with_next` can automatically unpack this tuple into separate lambda arguments. + ```cpp + auto countProducer = rpl::single(1); // Type: rpl::producer + auto textProducer = rpl::single(u"hello"_q); // Type: rpl::producer + rpl::lifetime lifetime; + + // rpl::combine takes producers by const-ref internally and duplicates, + // so move/duplicate is usually not strictly needed here unless you + // want to signal intent or manage the lifetime of p1/p2 explicitly. + auto combined = rpl::combine( + countProducer, // or rpl::duplicate(countProducer) + textProducer // or rpl::duplicate(textProducer) + ); + + // Starting the combined producer consumes it. + // The lambda receives unpacked arguments, not the tuple itself. + std::move( + combined + ) | rpl::start_with_next([=](int count, const QString &text) { + // No need for std::get<0>(latest), etc. + qDebug() << "Combined: Count=" << count << ", Text=" << text; + }, lifetime); + + // This also works with map, filter, etc. + std::move( + combined + ) | rpl::filter([=](int count, const QString &text) { + return count > 0 && !text.isEmpty(); + }) | rpl::map([=](int count, const QString &text) { + return text.repeated(count); + }) | rpl::start_with_next([=](const QString &result) { + qDebug() << "Mapped & Filtered: " << result; + }, lifetime); + ``` + +* `rpl::merge`: Merges the output of multiple producers of the *same type* into a single producer. It emits a value whenever *any* of the source producers emits a value. + ```cpp + auto sourceA = /* ... */; // Type: rpl::producer + auto sourceB = /* ... */; // Type: rpl::producer + + // rpl::merge also duplicates internally. + auto merged = rpl::merge(sourceA, sourceB); + + // Starting the merged producer consumes it. + std::move( + merged + ) | rpl::start_with_next([=](QString &&value) { + // Receives values from either sourceA or sourceB as they arrive. + qDebug() << "Merged value: " << value; + }, lifetime); + ``` + +## Key Concepts Summary + +* Use `rpl::producer` to represent streams of values. +* Manage subscription duration using `rpl::lifetime`. +* Pass `rpl::lifetime` to `rpl::start_...` methods. +* If `rpl::lifetime` is not passed, **store the returned lifetime** to keep the subscription active. +* Use operators like `| rpl::map`, `| rpl::filter` to transform streams. +* Use `rpl::combine` or `rpl::merge` to combine streams. +* When starting a chain (`std::move(producer) | rpl::map(...)`), explicitly move the initial producer. +* These functions typically duplicate their input producers internally. +* Starting a pipeline consumes the producer; use ` \ No newline at end of file diff --git a/.cursor/styling.md b/.cursor/styling.md new file mode 100644 index 0000000000..8e3578068f --- /dev/null +++ b/.cursor/styling.md @@ -0,0 +1,149 @@ +# Telegram Desktop UI Styling + +## Style Definition Files + +UI element styles (colors, fonts, paddings, margins, icons, etc.) are defined in `.style` files using a custom syntax. These files are located alongside the C++ source files they correspond to within specific UI component directories (e.g., `Telegram/SourceFiles/ui/chat/chat.style`). + +Definitions from other `.style` files can be included using the `using` directive at the top of the file: +```style +using "ui/basic.style"; +using "ui/widgets/widgets.style"; +``` + +The central definition of named colors happens in `Telegram/SourceFiles/ui/colors.palette`. This file allows for theme generation and loading colors from various sources. + +### Syntax Overview + +1. **Built-in Types:** The syntax recognizes several base types inferred from the value assigned: + * `int`: Integer numbers (e.g., `lineHeight: 20;`) + * `bool`: Boolean values (e.g., `useShadow: true;`) + * `pixels`: Pixel values, ending with `px` (e.g., `borderWidth: 1px;`). Generated as `int` in C++. + * `color`: Named colors defined in `colors.palette` (e.g., `background: windowBg;`) + * `icon`: Defined inline using a specific syntax (see below). Generates `style::icon`. + * `margins`: Four pixel values for margins or padding. Requires `margins(top, right, bottom, left)` syntax (e.g., `margin: margins(10px, 5px, 10px, 5px);` or `padding: margins(8px, 8px, 8px, 8px);`). Generates `style::margins` (an alias for `QMargins`). + * `size`: Two pixel values for width and height (e.g., `iconSize: size(16px, 16px);`). Generates `style::size`. + * `point`: Two pixel values for x and y coordinates (e.g., `textPos: point(5px, 2px);`). Generates `style::point`. + * `align`: Alignment keywords (e.g., `textAlign: align(center);` or `iconAlign: align(left);`). Generates `style::align`. + * `font`: Font definitions (e.g., `font: font(14px semibold);`). Generates `style::font`. + * `double`: Floating point numbers (e.g., `disabledOpacity: 0.5;`) + + *Note on Borders:* Borders are typically defined using multiple fields like `border: pixels;` (for width) and `borderFg: color;` (for color), rather than a single CSS-like property. + +2. **Structure Definition:** You can define complex data structures directly within the `.style` file: + ```style + MyButtonStyle { // Defines a structure named 'MyButtonStyle' + textPadding: margins; // Field 'textPadding' expects margins type + icon: icon; // Field 'icon' of type icon + height: pixels; // Field 'height' of type pixels + } + ``` + This generates a `struct MyButtonStyle { ... };` inside the `namespace style`. Fields will have corresponding C++ types (`style::margins`, `style::icon`, `int`). + +3. **Variable Definition & Inheritance:** Variables are defined using `name: value;` or `groupName { ... }`. They can be of built-in types or custom structures. Structures can be initialized inline or inherit from existing variables. + + **Icon Definition Syntax:** Icons are defined inline using the `icon{...}` syntax. The generator probes for `.svg` files or `.png` files (including `@2x`, `@3x` variants) based on the provided path stem. + ```style + // Single-part icon definition: + myIconSearch: icon{{ "gui/icons/search", iconColor }}; + // Multi-part icon definition (layers drawn bottom-up): + myComplexIcon: icon{ + { "gui/icons/background", iconBgColor }, + { "gui/icons/foreground", iconFgColor } + }; + // Icon with path modifiers (PNG only for flips, SVG only for size): + myFlippedIcon: icon{{ "gui/icons/arrow-flip_horizontal", arrowColor }}; + myResizedIcon: icon{{ "gui/icons/logo-128x128", logoColor }}; // Forces 128x128 for SVG + ``` + + **Other Variable Examples:** + ```style + // Simple variables + buttonHeight: 30px; + activeButtonColor: buttonBgActive; // Named color from colors.palette + + // Variable of a custom structure type, initialized inline + defaultButton: MyButtonStyle { + textPadding: margins(10px, 15px, 10px, 15px); // Use margins(...) syntax + icon: myIconSearch; // Assign the previously defined icon variable + height: buttonHeight; // Reference another variable + } + + // Another variable inheriting from 'defaultButton' and overriding/adding fields + primaryButton: MyButtonStyle(defaultButton) { + icon: myComplexIcon; // Override icon with the multi-part one + backgroundColor: activeButtonColor; // Add a field not in MyButtonStyle definition + } + + // Style group (often used for specific UI elements) + chatInput { // Example using separate border properties and explicit padding + border: 1px; // Border width + borderFg: defaultInputFieldBorder; // Border color (named color) + padding: margins(5px, 10px, 5px, 10px); // Use margins(...) syntax for padding field + backgroundColor: defaultChatBg; // Background color + } + ``` + +## Code Generation + +A code generation tool processes these `.style` files and `colors.palette` to create C++ objects. +- The `using` directives resolve dependencies between `.style` files. +- Custom structure definitions (like `MyButtonStyle`) generate corresponding `struct MyButtonStyle { ... };` within the `namespace style`. +- Style variables/groups (like `defaultButton`, `primaryButton`, `chatInput`) are generated as objects/structs within the `st` namespace (e.g., `st::defaultButton`, `st::primaryButton`, `st::chatInput`). These generated structs contain members corresponding to the fields defined in the `.style` file. +- Color objects are generated into the `st` namespace as well, based on their names in `colors.palette` (e.g., `st::windowBg`, `st::buttonBgActive`). +- The generated header files for styles are placed in the `Telegram/SourceFiles/styles/` directory with a `style_` prefix (e.g., `styles/style_widgets.h` for `ui/widgets/widgets.style`). You include them like `#include "styles/style_widgets.h"`. + +Generated C++ types correspond to the `.style` types: `style::color`, `style::font`, `style::margins` (used for both `margin:` and `padding:` fields), `style::icon`, `style::size`, `style::point`, `style::align`, and `int` or `bool` for simple types. + +## Style Usage in Code + +Styles are applied in C++ code by referencing the generated `st::...` objects and their members. + +```cpp +// Example: Including the generated style header +#include "styles/style_widgets.h" // For styles defined in ui/widgets/widgets.style + +// ... inside some UI class code ... + +// Accessing members of a generated style struct +int height = st::primaryButton.height; // Accessing the 'height' field (pixels -> int) +const style::icon &icon = st::primaryButton.icon; // Accessing the 'icon' field (st::myComplexIcon) +style::margins padding = st::primaryButton.textPadding; // Accessing 'textPadding' +style::color bgColor = st::primaryButton.backgroundColor; // Accessing the color (st::activeButtonColor) + +// Applying styles (conceptual examples) +myButton->setIcon(st::primaryButton.icon); +myButton->setHeight(st::primaryButton.height); +myButton->setPadding(st::primaryButton.textPadding); +myButton->setBackgroundColor(st::primaryButton.backgroundColor); + +// Using styles directly in painting +void MyWidget::paintEvent(QPaintEvent *e) { + Painter p(this); + p.fillRect(rect(), st::chatInput.backgroundColor); // Use color from chatInput style + + // Border painting requires width and color + int borderWidth = st::chatInput.border; // Access border width (pixels -> int) + style::color borderColor = st::chatInput.borderFg; // Access border color + if (borderWidth > 0) { + p.setPen(QPen(borderColor, borderWidth)); + // Adjust rect for pen width if needed before drawing + p.drawRect(rect().adjusted(borderWidth / 2, borderWidth / 2, -borderWidth / 2, -borderWidth / 2)); + } + + // Access padding (style::margins) + style::margins inputPadding = st::chatInput.padding; + // ... use inputPadding.top(), inputPadding.left() etc. for content layout ... +} +``` + +**Key Points:** + +* Styles are defined in `.style` files next to their corresponding C++ source files. +* `using "path/to/other.style";` includes definitions from other style files. +* Named colors are defined centrally in `ui/colors.palette`. +* `.style` syntax supports built-in types (like `pixels`, `color`, `margins`, `point`, `size`, `align`, `font`, `double`), custom structure definitions (`Name { field: type; ... }`), variable definitions (`name: value;`), and inheritance (`child: Name(parent) { ... }`). +* Values must match the expected type (e.g., fields declared as `margins` type, like `margin:` or `padding:`, require `margins(...)` syntax). Borders are typically set via separate `border: pixels;` and `borderFg: color;` fields. +* Icons are defined inline using `name: icon{{ "path_stem", color }};` or `name: icon{ { "path1", c1 }, ... };` syntax, with optional path modifiers. +* Code generation creates `struct` definitions in the `style` namespace for custom types and objects/structs in the `st` namespace for defined variables/groups. +* Generated headers are in `styles/` with a `style_` prefix and must be included. +* Access style properties via the generated `st::` objects (e.g., `st::primaryButton.height`, `st::chatInput.backgroundColor`). \ No newline at end of file diff --git a/.cursorignore b/.cursorignore new file mode 100644 index 0000000000..db6b53b765 --- /dev/null +++ b/.cursorignore @@ -0,0 +1,2 @@ +# Add directories or file patterns to ignore during indexing (e.g. foo/ or *.csv) +Telegram/ThirdParty/