Port the Edit-Style field into Create with AI

This commit is contained in:
John Preston
2026-07-01 15:19:16 +04:00
parent 1ada3384f4
commit 4dbb24ed25
4 changed files with 106 additions and 88 deletions
-8
View File
@@ -1590,14 +1590,6 @@ aiComposeBoxInfoButton: IconButton(boxTitleClose) {
}
aiComposeShadowOpacity: 0.3;
createAiPromptField: InputField(aiTonePromptField) {
textBg: aiComposeCardBg;
textBgActive: aiComposeCardBg;
borderRadius: 22px;
textMargins: margins(16px, 16px, 16px, 16px);
heightMin: 54px;
heightMax: 240px;
}
createAiSelectorArrowsIcon: icon{{ "arrows_select", windowSubTextFg }};
createAiReloadIcon: icon{{ "chat/refresh-18x18", windowFgActive }};
createAiReloadButton: RoundButton(defaultActiveButton) {
+20 -5
View File
@@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "apiwrap.h"
#include "base/object_ptr.h"
#include "base/weak_ptr.h"
#include "boxes/create_ai_tone_box.h"
#include "chat_helpers/compose/compose_show.h"
#include "data/data_file_origin.h"
#include "data/data_msg_id.h"
@@ -33,6 +34,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/widgets/checkbox.h"
#include "ui/widgets/fields/input_field.h"
#include "ui/widgets/labels.h"
#include "ui/wrap/padding_wrap.h"
#include "window/themes/window_theme.h"
#include "styles/style_boxes.h"
#include "styles/style_chat.h"
@@ -350,18 +352,31 @@ void CreateAiBox(not_null<Ui::GenericBox*> box, CreateAiBoxArgs &&args) {
});
box->setStyle(st::aiComposeBox);
const auto prompt = box->setPinnedToTopContent(
object_ptr<Ui::InputField>(
const auto promptWrap = box->setPinnedToTopContent(
object_ptr<Ui::PaddingWrap<Ui::InputField>>(
box,
st::createAiPromptField,
Ui::InputField::Mode::MultiLine,
tr::lng_ai_compose_create_placeholder()));
object_ptr<Ui::InputField>(
box,
st::aiTonePromptField,
Ui::InputField::Mode::MultiLine,
rpl::producer<QString>()),
st::aiToneFieldsMargin));
const auto prompt = promptWrap->entity();
prompt->setSubmitSettings(Ui::InputField::SubmitSettings::None);
prompt->setMaxLength(state->session->appConfig().get<int>(
u"aicompose_tone_prompt_length_max"_q,
1024));
state->prompt = prompt;
const auto promptPlaceholder = AddAiComposeFieldDecor(
prompt,
tr::lng_ai_compose_create_placeholder());
promptPlaceholder->heightValue(
) | rpl::on_next([=](int phHeight) {
const auto pad = st::aiToneFieldPadding;
prompt->setMinHeight(phHeight + pad.top() + pad.bottom());
}, prompt->lifetime());
const auto chooseLanguage = [=] {
box->uiShow()->showBox(Box([=](not_null<Ui::GenericBox*> chooser) {
Ui::ChooseLanguageBox(
@@ -163,6 +163,79 @@ void ChooseToneIconBox(
} // namespace
not_null<Ui::FlatLabel*> AddAiComposeFieldDecor(
not_null<Ui::InputField*> field,
rpl::producer<QString> placeholder) {
struct FieldDecor {
not_null<Ui::RpWidget*> bg;
not_null<Ui::FlatLabel*> placeholder;
Ui::Animations::Simple anim;
bool hidden = false;
};
const auto parent = field->parentWidget();
const auto decor = field->lifetime().make_state<FieldDecor>(FieldDecor{
.bg = Ui::CreateChild<Ui::RpWidget>(parent),
.placeholder = Ui::CreateChild<Ui::FlatLabel>(
parent,
std::move(placeholder),
st::aiTonePlaceholderLabel),
});
decor->bg->setAttribute(Qt::WA_TransparentForMouseEvents);
decor->placeholder->setAttribute(Qt::WA_TransparentForMouseEvents);
decor->bg->paintRequest(
) | rpl::on_next([bg = decor->bg] {
auto p = QPainter(bg);
auto hq = PainterHighQualityEnabler(p);
p.setPen(Qt::NoPen);
p.setBrush(st::aiToneFieldBg);
const auto r = st::aiToneFieldRadius;
p.drawRoundedRect(bg->rect(), r, r);
}, decor->bg->lifetime());
decor->bg->lower();
decor->placeholder->raise();
const auto applyPosition = [=] {
const auto pad = st::aiToneFieldPadding;
const auto progress = decor->anim.value(decor->hidden ? 1. : 0.);
const auto shift = int(base::SafeRound(
progress * (-st::defaultInputField.placeholderShift)));
decor->placeholder->moveToLeft(
field->x() + pad.left() + shift,
field->y() + pad.top());
decor->placeholder->setOpacity(1. - progress);
};
field->geometryValue(
) | rpl::on_next([=](QRect g) {
if (g.isEmpty()) {
return;
}
const auto pad = st::aiToneFieldPadding;
decor->bg->setGeometry(g);
decor->placeholder->resizeToWidth(
g.width() - pad.left() - pad.right());
applyPosition();
}, field->lifetime());
const auto animate = [=](bool hidden) {
if (decor->hidden == hidden) {
return;
}
decor->hidden = hidden;
decor->anim.start(
applyPosition,
hidden ? 0. : 1.,
hidden ? 1. : 0.,
st::defaultInputField.duration);
};
field->changes(
) | rpl::on_next([=] {
animate(!field->getLastText().isEmpty());
}, field->lifetime());
decor->hidden = !field->getLastText().isEmpty();
applyPosition();
return decor->placeholder;
}
not_null<Ui::AbstractButton*> AddAiToneIconPreview(
not_null<Ui::VerticalLayout*> container,
not_null<Main::Session*> session,
@@ -391,80 +464,8 @@ void SetupToneBox(
u"aicompose_tone_prompt_length_max"_q,
1024));
struct FieldDecor {
not_null<Ui::RpWidget*> bg;
not_null<Ui::FlatLabel*> placeholder;
Ui::Animations::Simple anim;
bool hidden = false;
};
const auto makeDecor = [=](
not_null<Ui::InputField*> field,
rpl::producer<QString> placeholderText) {
const auto parent = field->parentWidget();
const auto decor = field->lifetime().make_state<FieldDecor>(FieldDecor{
.bg = Ui::CreateChild<Ui::RpWidget>(parent),
.placeholder = Ui::CreateChild<Ui::FlatLabel>(
parent,
std::move(placeholderText),
st::aiTonePlaceholderLabel),
});
decor->bg->setAttribute(Qt::WA_TransparentForMouseEvents);
decor->placeholder->setAttribute(Qt::WA_TransparentForMouseEvents);
decor->bg->paintRequest(
) | rpl::on_next([bg = decor->bg] {
auto p = QPainter(bg);
auto hq = PainterHighQualityEnabler(p);
p.setPen(Qt::NoPen);
p.setBrush(st::aiToneFieldBg);
const auto r = st::aiToneFieldRadius;
p.drawRoundedRect(bg->rect(), r, r);
}, decor->bg->lifetime());
decor->bg->lower();
decor->placeholder->raise();
const auto applyPosition = [=] {
const auto pad = st::aiToneFieldPadding;
const auto progress = decor->anim.value(decor->hidden ? 1. : 0.);
const auto shift = int(base::SafeRound(
progress * (-st::defaultInputField.placeholderShift)));
decor->placeholder->moveToLeft(
field->x() + pad.left() + shift,
field->y() + pad.top());
decor->placeholder->setOpacity(1. - progress);
};
field->geometryValue(
) | rpl::on_next([=](QRect g) {
if (g.isEmpty()) {
return;
}
const auto pad = st::aiToneFieldPadding;
decor->bg->setGeometry(g);
decor->placeholder->resizeToWidth(
g.width() - pad.left() - pad.right());
applyPosition();
}, field->lifetime());
const auto animate = [=](bool hidden) {
if (decor->hidden == hidden) {
return;
}
decor->hidden = hidden;
decor->anim.start(
applyPosition,
hidden ? 0. : 1.,
hidden ? 1. : 0.,
st::defaultInputField.duration);
};
field->changes(
) | rpl::on_next([=] {
animate(!field->getLastText().isEmpty());
}, field->lifetime());
decor->hidden = !field->getLastText().isEmpty();
applyPosition();
return decor;
};
makeDecor(name, tr::lng_ai_compose_tone_name_placeholder());
const auto promptDecor = makeDecor(
AddAiComposeFieldDecor(name, tr::lng_ai_compose_tone_name_placeholder());
const auto promptPlaceholder = AddAiComposeFieldDecor(
prompt,
tr::lng_ai_compose_tone_prompt_placeholder());
@@ -500,7 +501,7 @@ void SetupToneBox(
rpl::combine(
prompt->topValue(),
promptDecor->placeholder->heightValue(),
promptPlaceholder->heightValue(),
box->getDelegate()->contentHeightMaxValue()
) | rpl::on_next([=](int top, int phHeight, int contentHeight) {
const auto pad = st::aiToneFieldPadding;
@@ -17,11 +17,21 @@ class Session;
namespace Ui {
class AbstractButton;
class FlatLabel;
class GenericBox;
class InputField;
class Show;
class VerticalLayout;
} // namespace Ui
// Turns an Ui::InputField into a tone-style island: a rounded opaque
// background painted behind the field plus a gray placeholder label that
// fades and shifts on typing. Returns the placeholder label so callers can
// observe its height (e.g. for auto-grow min-height computations).
not_null<Ui::FlatLabel*> AddAiComposeFieldDecor(
not_null<Ui::InputField*> field,
rpl::producer<QString> placeholder);
not_null<Ui::AbstractButton*> AddAiToneIconPreview(
not_null<Ui::VerticalLayout*> container,
not_null<Main::Session*> session,