Ensure edit control font size matches font size of text being edited.

Before this commit, the position of the edit box was adjusted
by trial and error, as far as I can tell. This commit changes
the positioning machinery for edit controls as follows:

The coordinates passed to ShowTextEditControl/ShowGraphicsEditControl
now denote: X the left bound, and Y the baseline.

The font height passed to ShowGraphicsEditControl denotes
the absolute font height in pixels, i.e. ascent plus descent.

Platform-dependent code uses these coordinates, the font metrics
for the font appropriate for the platform, and the knowledge of
the decorations drawn around the text by the native edit control
to position the edit control in a way that overlays the text inside
the edit control with the rendered text.

On OS X, GNU Unifont (of height 16) has metrics identical to
Monaco (of height 15) and so as an exception, the edit control
is nudged slightly for a pixel-perfect fit.

Also, since the built-in vector font is proportional, this commit
also switches the edit control font to proportional when editing
constraints.
pull/4/head
whitequark 2016-04-12 14:24:09 +00:00
parent 0f304b4c64
commit d17771064a
6 changed files with 101 additions and 44 deletions

View File

@ -133,7 +133,8 @@ void SolveSpace::ScheduleLater() {
@property BOOL wantsBackingStoreScaling;
@property(readonly, getter=isEditing) BOOL editing;
- (void)startEditing:(NSString*)text at:(NSPoint)origin;
- (void)startEditing:(NSString*)text at:(NSPoint)origin
withSize:(double)fontSize usingMonospace:(BOOL)isMonospace;
- (void)stopEditing;
- (void)didEdit:(NSString*)text;
@end
@ -160,6 +161,8 @@ void SolveSpace::ScheduleLater() {
editor = [[NSTextField alloc] init];
[editor setEditable:YES];
[[editor cell] setUsesSingleLineMode:YES];
[editor setBezeled:NO];
[editor setTarget:self];
[editor setAction:@selector(editorAction:)];
@ -202,7 +205,6 @@ CONVERT(Rect)
offscreen = new GLOffscreen;
NSSize size = [self convertSizeToBacking:[self bounds].size];
NSRect bounds = [self convertRectToBacking:[self bounds]];
offscreen->begin(size.width, size.height);
[self drawGL];
@ -225,12 +227,24 @@ CONVERT(Rect)
@synthesize editing;
- (void)startEditing:(NSString*)text at:(NSPoint)origin {
- (void)startEditing:(NSString*)text at:(NSPoint)origin
withSize:(double)fontSize usingMonospace:(BOOL)isMonospace {
if(!self->editing) {
[self addSubview:editor];
self->editing = YES;
}
NSFont *font;
if(isMonospace)
font = [NSFont fontWithName:@"Monaco" size:fontSize];
else
font = [NSFont controlContentFontOfSize:fontSize];
[editor setFont:font];
origin.x -= 3; /* left padding; no way to get it from NSTextField */
origin.y -= [editor intrinsicContentSize].height;
origin.y += [editor baselineOffsetFromBottom];
[editor setFrameOrigin:origin];
[editor setStringValue:text];
[self prepareEditor];
@ -251,9 +265,10 @@ CONVERT(Rect)
}
- (void)prepareEditor {
CGFloat intrinsicContentHeight = [editor intrinsicContentSize].height;
[editor setFrameSize:(NSSize){
.width = 100,
.height = [editor intrinsicContentSize].height }];
.width = intrinsicContentHeight * 12,
.height = intrinsicContentHeight }];
}
- (void)didEdit:(NSString*)text {
@ -382,18 +397,20 @@ CONVERT(Rect)
[super keyDown:event];
}
- (void)startEditing:(NSString*)text at:(NSPoint)xy {
- (void)startEditing:(NSString*)text at:(NSPoint)xy withSize:(double)fontSize {
// Convert to ij (vs. xy) style coordinates
NSSize size = [self convertSizeToBacking:[self bounds].size];
NSPoint point = {
.x = xy.x + size.width / 2,
.y = xy.y - size.height / 2 + [editor intrinsicContentSize].height
.y = xy.y - size.height / 2
};
[super startEditing:text at:[self convertPointFromBacking:point]];
[super startEditing:text at:[self convertPointFromBacking:point]
withSize:fontSize usingMonospace:FALSE];
}
- (void)didEdit:(NSString*)text {
SolveSpace::SS.GW.EditControlDone([text UTF8String]);
[self setNeedsDisplay:YES];
}
- (void)cancelOperation:(id)sender {
@ -487,9 +504,10 @@ bool FullScreenIsActive(void) {
return [GWDelegate isFullscreen];
}
void ShowGraphicsEditControl(int x, int y, const std::string &str) {
void ShowGraphicsEditControl(int x, int y, int fontSize, const std::string &str) {
[GWView startEditing:[NSString stringWithUTF8String:str.c_str()]
at:(NSPoint){(CGFloat)x, (CGFloat)y}];
at:(NSPoint){(CGFloat)x, (CGFloat)y}
withSize:fontSize];
}
void HideGraphicsEditControl(void) {
@ -937,8 +955,8 @@ SolveSpace::DialogChoice SolveSpace::LocateImportedFileYesNoCancel(
- (void)startEditing:(NSString*)text at:(NSPoint)point {
point = [self convertPointFromBacking:point];
point.y = -point.y;
[super startEditing:text at:point];
point.y = -point.y + 2;
[super startEditing:text at:point withSize:15.0 usingMonospace:TRUE];
[[self window] makeKeyWindow];
[[self window] makeFirstResponder:editor];
}

View File

@ -372,24 +372,44 @@ public:
EditorOverlay(Gtk::Widget &underlay) : _underlay(underlay) {
add(_underlay);
Pango::FontDescription desc;
desc.set_family("monospace");
desc.set_size(7000);
#ifdef HAVE_GTK3
_entry.override_font(desc);
#else
_entry.modify_font(desc);
#endif
_entry.set_width_chars(30);
_entry.set_no_show_all(true);
_entry.set_has_frame(false);
add(_entry);
_entry.signal_activate().
connect(sigc::mem_fun(this, &EditorOverlay::on_activate));
}
void start_editing(int x, int y, const std::string &val) {
move(_entry, x, y - 4);
void start_editing(int x, int y, int font_height,
bool is_monospace, const std::string &val) {
Pango::FontDescription font_desc;
font_desc.set_family(is_monospace ? "monospace" : "normal");
font_desc.set_absolute_size(font_height * Pango::SCALE);
#ifdef HAVE_GTK3
_entry.override_font(font_desc);
#else
_entry.modify_font(font_desc);
#endif
/* y coordinate denotes baseline */
Pango::FontMetrics font_metrics = get_pango_context()->get_metrics(font_desc);
y -= font_metrics.get_ascent() / Pango::SCALE;
#ifdef HAVE_GTK3
Gtk::Border border = _entry.get_style_context()->get_padding();
move(_entry, x - border.get_left(), y - border.get_top());
#else
/* We need _gtk_entry_effective_inner_border, but it's not
in the public API, so emulate its logic. */
Gtk::Border border = { 2, 2, 2, 2 }, *style_border;
gtk_widget_style_get(GTK_WIDGET(_entry.gobj()), "inner-border",
&style_border, NULL);
if(style_border) border = *style_border;
move(_entry, x - border.left, y - border.top);
#endif
_entry.set_text(val);
if(!_entry.is_visible()) {
_entry.show();
@ -721,16 +741,16 @@ bool FullScreenIsActive(void) {
return GW->is_fullscreen();
}
void ShowGraphicsEditControl(int x, int y, const std::string &val) {
void ShowGraphicsEditControl(int x, int y, int fontHeight, const std::string &val) {
Gdk::Rectangle rect = GW->get_widget().get_allocation();
// Convert to ij (vs. xy) style coordinates,
// and compensate for the input widget height due to inverse coord
int i, j;
i = x + rect.get_width() / 2;
j = -y + rect.get_height() / 2 - 24;
j = -y + rect.get_height() / 2;
GW->get_overlay().start_editing(i, j, val);
GW->get_overlay().start_editing(i, j, fontHeight, /*is_monospace=*/false, val);
}
void HideGraphicsEditControl(void) {
@ -1414,7 +1434,8 @@ void SetMousePointerToHand(bool is_hand) {
}
void ShowTextEditControl(int x, int y, const std::string &val) {
TW->get_overlay().start_editing(x, y, val);
TW->get_overlay().start_editing(x, y, TextWindow::CHAR_HEIGHT,
/*is_monospace=*/true, val);
}
void HideTextEditControl(void) {

View File

@ -1292,7 +1292,11 @@ void GraphicsWindow::MouseLeftDoubleClick(double mx, double my) {
break;
}
}
ShowGraphicsEditControl((int)p2.x, (int)p2.y-4, edit_value);
hStyle hs = c->disp.style;
if(hs.v == 0) hs.v = Style::CONSTRAINT;
ShowGraphicsEditControl((int)p2.x, (int)p2.y,
ssglStrFontSize(Style::TextHeight(hs)) * scale,
edit_value);
}
}

View File

@ -235,7 +235,7 @@ void CheckMenuById(int id, bool checked);
void RadioMenuById(int id, bool selected);
void EnableMenuById(int id, bool enabled);
void ShowGraphicsEditControl(int x, int y, const std::string &str);
void ShowGraphicsEditControl(int x, int y, int fontHeight, const std::string &str);
void HideGraphicsEditControl(void);
bool GraphicsEditControlIsVisible(void);
void ShowTextEditControl(int x, int y, const std::string &str);

View File

@ -88,7 +88,7 @@ void TextWindow::ShowEditControl(int col, const std::string &str, int halfRow) {
int x = LEFT_MARGIN + CHAR_WIDTH*col;
int y = (halfRow - SS.TW.scrollPos)*(LINE_HEIGHT/2);
ShowTextEditControl(x - 3, y + 2, str);
ShowTextEditControl(x, y + 18, str);
}
void TextWindow::ShowEditControlWithColorPicker(int col, RgbaColor rgb)

View File

@ -21,10 +21,6 @@
# undef uint32_t // thanks but no thanks
#endif
// For the edit controls
#define EDIT_WIDTH 220
#define EDIT_HEIGHT 21
HINSTANCE Instance;
HWND TextWnd;
@ -803,8 +799,29 @@ void SolveSpace::InvalidateText(void)
InvalidateRect(TextWnd, NULL, false);
}
static void ShowEditControl(HWND h, int x, int y, const std::wstring &s) {
MoveWindow(h, x, y, EDIT_WIDTH, EDIT_HEIGHT, true);
static void ShowEditControl(HWND h, int x, int y, int fontHeight,
bool isMonospace, const std::wstring &s) {
static HFONT hf;
if(hf) DeleteObject(hf);
hf = CreateFontW(-fontHeight, 0, 0, 0,
FW_REGULAR, false, false, false, ANSI_CHARSET,
OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS,
DEFAULT_QUALITY, FF_DONTCARE, isMonospace ? L"Lucida Console" : L"Arial");
if(hf) SendMessage(h, WM_SETFONT, (WPARAM)hf, false);
else SendMessage(h, WM_SETFONT, (WPARAM)(HFONT)GetStockObject(SYSTEM_FONT), false);
SendMessage(h, EM_SETMARGINS, EC_LEFTMARGIN|EC_RIGHTMARGIN, 0);
TEXTMETRICW tm;
HDC hdc = GetDC(h);
SelectObject(hdc, hf);
GetTextMetrics(hdc, &tm);
ReleaseDC(h, hdc);
y -= tm.tmAscent; /* y coordinate denotes baseline */
RECT rc = { x, y, x + tm.tmAveCharWidth * 30, y + tm.tmHeight };
AdjustWindowRectEx(&rc, 0, false, WS_EX_CLIENTEDGE);
MoveWindow(h, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top, true);
ShowWindow(h, SW_SHOW);
if(!s.empty()) {
SendMessage(h, WM_SETTEXT, 0, (LPARAM)s.c_str());
@ -816,7 +833,8 @@ void SolveSpace::ShowTextEditControl(int x, int y, const std::string &str)
{
if(GraphicsEditControlIsVisible()) return;
ShowEditControl(TextEditControl, x, y, Widen(str));
ShowEditControl(TextEditControl, x, y, TextWindow::CHAR_HEIGHT,
/*isMonospace=*/true, Widen(str));
}
void SolveSpace::HideTextEditControl(void)
{
@ -826,7 +844,8 @@ bool SolveSpace::TextEditControlIsVisible(void)
{
return IsWindowVisible(TextEditControl) ? true : false;
}
void SolveSpace::ShowGraphicsEditControl(int x, int y, const std::string &str)
void SolveSpace::ShowGraphicsEditControl(int x, int y, int fontHeight,
const std::string &str)
{
if(GraphicsEditControlIsVisible()) return;
@ -835,11 +854,8 @@ void SolveSpace::ShowGraphicsEditControl(int x, int y, const std::string &str)
x = x + (r.right - r.left)/2;
y = (r.bottom - r.top)/2 - y;
// (x, y) are the bottom left, but the edit control is placed by its
// top left corner
y -= 20;
ShowEditControl(GraphicsEditControl, x, y, Widen(str));
ShowEditControl(GraphicsEditControl, x, y, fontHeight,
/*isMonospace=*/false, Widen(str));
}
void SolveSpace::HideGraphicsEditControl(void)
{
@ -1250,7 +1266,6 @@ static void CreateMainWindows(void)
GraphicsEditControl = CreateWindowExW(WS_EX_CLIENTEDGE, WC_EDIT, L"",
WS_CHILD | ES_AUTOHSCROLL | WS_TABSTOP | WS_CLIPSIBLINGS,
50, 50, 100, 21, GraphicsWnd, NULL, Instance, NULL);
SendMessage(GraphicsEditControl, WM_SETFONT, (WPARAM)FixedFont, true);
// The text window, with a comand line and some textual information
// about the sketch.
@ -1277,7 +1292,6 @@ static void CreateMainWindows(void)
TextEditControl = CreateWindowExW(WS_EX_CLIENTEDGE, WC_EDIT, L"",
WS_CHILD | ES_AUTOHSCROLL | WS_TABSTOP | WS_CLIPSIBLINGS,
50, 50, 100, 21, TextWnd, NULL, Instance, NULL);
SendMessage(TextEditControl, WM_SETFONT, (WPARAM)FixedFont, true);
// Now that all our windows exist, set up gl contexts.
CreateGlContext(TextWnd, &TextGl);